Skip to content

Productize neural-mesh as a Samsarix release candidate - #2

Merged
Deathcharge merged 5 commits into
mainfrom
codex/productize-neural-mesh
Jul 29, 2026
Merged

Productize neural-mesh as a Samsarix release candidate#2
Deathcharge merged 5 commits into
mainfrom
codex/productize-neural-mesh

Conversation

@Deathcharge

@Deathcharge Deathcharge commented Jul 29, 2026

Copy link
Copy Markdown
Owner

What changed

  • rebuilt the original extraction as a standalone, dependency-free Python consensus library
  • added bounded provider orchestration, transparent complete-link agreement analysis, structured outcomes, opt-in privacy-minimized JSONL usage, and a typed public API
  • added comprehensive tests, pinned lint/type/build tooling, Linux/Windows CI, artifact inspection, and clean-wheel smoke coverage
  • added an installed neural-mesh CLI with version reporting and a credential-free offline demo
  • updated current company, package, support, and license identity to Samsarix LLC
  • added SECURITY.md, a release runbook, and a commit-pinned trusted-publishing workflow with separate build, provenance, and protected OIDC publication jobs
  • corrected Python 3.10 timeout classification discovered by the first hosted matrix run
  • updated every GitHub workflow dependency to a current, full-SHA-pinned Node 24 release

Why

The previous repository was an incomplete extraction: it depended on private sibling code, declared unavailable and unrelated dependencies, did not ship runtime code in its wheel, and treated successful provider calls as consensus. This change turns it into an independently installable and auditable release candidate while keeping provider credentials, networking, retries, and persistence under explicit application control.

Developer and user impact

Consumers can install the package, supply ordinary sync or async provider adapters, receive inspectable per-provider outcomes, and run neural-mesh demo without credentials or network access. The package remains version 0.2.0 and is not published to PyPI by this PR. Publication still requires the protected pypi GitHub environment, matching PyPI trusted-publisher coordinates, a signed v0.2.0 tag, and explicit release approval.

The checked-in license remains a custom Business Source License 1.1 variant. Its substantive production threshold, Change Date, and Change License were preserved; current identity fields now name neural-mesh and Samsarix LLC.

Verification

  • Ruff 0.9.10 formatting and lint: passed
  • mypy 1.14.1 strict checks: passed across 11 source/test/example files
  • pytest 8.3.5: 42 passed, 1 Windows-inapplicable POSIX-mode assertion skipped locally
  • branch coverage: 97.88% (95% required)
  • isolated sdist/wheel build and Twine metadata validation: passed
  • exact final wheel smoke: passed on Python 3.11.9 and 3.13.14 outside the checkout
  • installed console and module entry points plus the offline demo: passed
  • CI, release, and Dependabot YAML parsed successfully; all 11 workflow action references are pinned to full commit SHAs
  • hosted CI run 30413960450: passed on Linux Python 3.10, 3.11, 3.12, and 3.13; Windows Python 3.11; and the distribution build/install smoke job

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added provider-agnostic consensus workflows with structured outcomes, agreement scoring, timeouts, and bounded concurrency.
    • Added an offline CLI demo and Python package entrypoint.
    • Added optional privacy-minimized usage tracking and statistics.
    • Added typed public APIs and Python 3.10+ support.
  • Documentation

    • Updated README, changelog, security guidance, contribution instructions, roadmap, and release documentation.
    • Added an offline consensus example.
  • Bug Fixes

    • Improved timeout, cancellation, validation, error handling, and response classification.
  • Chores

    • Added automated quality checks, release publishing, provenance attestation, and dependency update configuration.

Walkthrough

The repository is converted into a typed, dependency-free neural-mesh package with bounded consensus execution, optional JSONL usage persistence, an offline CLI demo, modern packaging, comprehensive tests, documentation, and CI/release automation.

Changes

neural-mesh productization

Layer / File(s) Summary
Package contracts and distribution
pyproject.toml, neural_mesh/__init__.py, neural_mesh/multi_ai_consensus.py, neural_mesh/py.typed, requirements*.txt, MANIFEST.in, LICENSE, .gitignore
Defines the typed public API, compatibility aliases, package metadata, CLI entrypoint, dependency policy, manifest, and tooling configuration.
Consensus execution and usage persistence
neural_mesh/consensus.py, neural_mesh/usage.py, tests/test_consensus.py, tests/test_usage.py
Adds bounded provider orchestration, structured agreement results, validation, cancellation handling, optional JSONL persistence, aggregation, and filesystem safety checks.
CLI and offline integration path
neural_mesh/cli.py, neural_mesh/__main__.py, examples/basic_consensus.py, tests/test_cli.py, tests/test_public_api.py
Adds the installed CLI, deterministic demo providers, module execution, and tests for CLI and example behavior.
CI, artifact validation, and publishing
.github/workflows/*, .github/dependabot.yml
Adds matrix quality checks, wheel smoke tests, release validation, provenance attestation, PyPI publishing, and Dependabot updates.
Documentation and project governance
README.md, CONTRIBUTING.md, SECURITY.md, CHANGELOG.md, ROADMAP.md, docs/*
Documents usage, architecture, security boundaries, contribution rules, licensing, release procedures, productization evidence, roadmap, and limitations.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: productizing neural-mesh as a Samsarix release candidate.
Description check ✅ Passed The description is directly aligned with the changeset and accurately summarizes the implementation, docs, CI, and release workflow updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/productize-neural-mesh

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Deathcharge
Deathcharge marked this pull request as ready for review July 29, 2026 11:39

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 73-80: Update the wheel inspection and installation steps to
derive the artifact path from the built files or package metadata instead of
hardcoding neural_mesh-0.2.0-py3-none-any.whl. Reuse the derived path for both
python -m zipfile -l and pip install, while preserving the existing smoke-test
commands in the Install wheel outside the checkout step.

In @.github/workflows/release.yml:
- Around line 61-64: Document the sensitive permissions in the attestation and
publish jobs of the workflow: add concise inline rationale comments for each
id-token: write and attestations: write entry, including the publish job’s
id-token: write. Keep the existing permission values and scopes unchanged.
- Around line 26-30: Remove the cache: pip setting from the actions/setup-python
step in the release workflow, while retaining the Python version and
cache-dependency-path configuration only if still valid without caching. Do not
alter unrelated CI workflows or release build steps.

In `@neural_mesh/consensus.py`:
- Around line 723-738: Update _usage_record so task_sha256 is derived with a
per-installation secret via keyed HMAC rather than bare hashlib.sha256, using
the existing configuration or secret-management mechanism and preserving the
hexadecimal digest format expected by UsageRecord. Ensure the same stable secret
is reused across records while avoiding persistence of the raw task label.
- Around line 285-297: Move semaphore creation out of ConsensusEngine.__init__
and into run(), creating a fresh asyncio.Semaphore using
self.config.max_concurrency for each invocation. Update run() and any related
helpers to use that per-call semaphore, ensuring separate asyncio.run() event
loops never share the same semaphore.

In `@pyproject.toml`:
- Around line 100-104: Update the Ruff configuration in the [tool.ruff.lint]
section so the tests/** per-file-ignore for S101 is meaningful: either add the S
category to select if Bandit checks are intended, or remove the S101 ignore
entry if they are not.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fd12bb47-9255-4dd9-93e8-5ba85faabdb7

📥 Commits

Reviewing files that changed from the base of the PR and between 4c33505 and 39c934d.

📒 Files selected for processing (29)
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .gitignore
  • CHANGELOG.md
  • CONTRIBUTING.md
  • LICENSE
  • MANIFEST.in
  • README.md
  • ROADMAP.md
  • SECURITY.md
  • docs/PRODUCTIZATION.md
  • docs/RELEASING.md
  • examples/basic_consensus.py
  • neural_mesh/__init__.py
  • neural_mesh/__main__.py
  • neural_mesh/cli.py
  • neural_mesh/consensus.py
  • neural_mesh/multi_ai_consensus.py
  • neural_mesh/py.typed
  • neural_mesh/usage.py
  • pyproject.toml
  • requirements-dev.txt
  • requirements.txt
  • setup.py
  • tests/test_cli.py
  • tests/test_consensus.py
  • tests/test_public_api.py
  • tests/test_usage.py
💤 Files with no reviewable changes (1)
  • setup.py

Comment thread .github/workflows/ci.yml
Comment on lines +73 to +80
run: python -m zipfile -l dist/neural_mesh-0.2.0-py3-none-any.whl
- name: Install wheel outside the checkout
run: |
python -m venv "${RUNNER_TEMP}/neural-mesh-smoke"
"${RUNNER_TEMP}/neural-mesh-smoke/bin/python" -m pip install --no-deps dist/neural_mesh-0.2.0-py3-none-any.whl
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/neural-mesh-smoke/bin/python" -c "import neural_mesh; print(neural_mesh.__version__)"
"${RUNNER_TEMP}/neural-mesh-smoke/bin/neural-mesh" --version

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Derive the wheel path instead of hardcoding 0.2.0.

The job builds the version declared by package metadata but inspects and installs only neural_mesh-0.2.0-py3-none-any.whl. The next version bump will make CI fail before validating the newly built artifact.

Use a metadata- or glob-derived wheel path for both inspection and installation.

As per the release workflow contract, package versions are read from pyproject.toml rather than fixed in automation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 73 - 80, Update the wheel inspection
and installation steps to derive the artifact path from the built files or
package metadata instead of hardcoding neural_mesh-0.2.0-py3-none-any.whl. Reuse
the derived path for both python -m zipfile -l and pip install, while preserving
the existing smoke-test commands in the Install wheel outside the checkout step.

Comment on lines +26 to +30
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
cache: pip
cache-dependency-path: requirements-dev.txt

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,220p'
else
  echo ".github/workflows/release.yml not found"
fi

echo
echo "== all release workflow references =="
rg -n "setup-python|cache=|pip|build|attest|publish|permissions:" .github/workflows/release.yml || true

echo
echo "== zizmor references if any =="
rg -n "zizmor|cache|poison|pip cache" -S . || true

echo
echo "== git status/stat =="
git status --short
git diff --stat

Repository: Deathcharge/neural-mesh

Length of output: 1932


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo availability =="
pwd
git rev-parse --show-toplevel || true
git status --short 2>/dev/null || true
git ls-files 2>/dev/null | head -50 || true

echo
echo "== workflow excerpt =="
if [ -f .github/workflows/release.yml ]; then
  nl -ba .github/workflows/release.yml | sed -n '1,220p'
else
  echo ".github/workflows/release.yml not found"
fi

echo
echo "== setup/cache/build references in release workflow =="
rg -n "setup-python|cache=|pip|build|attest|publish|permissions:" .github/workflows/release.yml || true

echo
echo "== zizmor references in repo =="
rg -n "zizmor|cache poisoning|cache-poisoning|pip cache|cache-dependency-path" -S . || true

Repository: Deathcharge/neural-mesh

Length of output: 1932


🌐 Web query:

GitHub Actions pip cache poisoning setup-python cache cache-dependency-path cache key cache poisoning

💡 Result:

GitHub Actions cache poisoning occurs when an untrusted workflow (such as one triggered by a Pull Request) writes malicious or corrupted dependencies into the shared action cache [1][2]. Subsequent, more privileged workflows (like those on the main branch) may then restore this poisoned cache, potentially leading to unauthorized code execution [1][2]. For actions/setup-python, the cache input enables built-in caching for package managers like pip, pipenv, and poetry [3][4]. By default, this action calculates a cache key based on the hash of dependency files (e.g., requirements.txt or pyproject.toml) [3][5]. The cache-dependency-path input allows you to specify custom dependency files or directories to include in this hash, providing more granular control over what triggers a cache miss or hit [3][6]. To mitigate cache poisoning risks, follow these practices: 1. Use Read-Only Mode: When running workflows on untrusted code (e.g., Pull Requests), configure the action to use the cache without writing to it [7]. The actions/setup-python action supports a cache-write input (or similar mechanisms depending on the version) that can be set to false for PRs [7][2]. Example configuration: - uses: actions/setup-python@v6 with: python-version: "3.12" cache: pip cache-write: ${{ github.event_name!= 'pull_request' }} 2. Separate Caches: Ensure that sensitive, privileged workflows (like those that publish packages or artifacts) do not share write access to the same cache keys used by PR-triggered workflows [8][2]. 3. Validate Dependencies: Always review code changes in PRs before labeling them as safe for execution in environments where secrets or sensitive permissions are present [1]. By disabling cache writes on untrusted PRs, you prevent them from modifying the shared cache that the main branch relies on, effectively neutralizing the poisoning vector [7][2].

Citations:


🌐 Web query:

actions/setup-python cache-write input version 7.0.0

💡 Result:

The actions/setup-python action does not currently have a cache-write input. While a pull request was proposed to add a cache-write input to support read-only cache mode [1][2], this feature has not been merged into the official release as of version 7.0.0 [3][4][5]. In version 7.0.0, which was released on July 20, 2026, the action continues to manage caching automatically based on the cache input and the cache-dependency-path configuration [3][5]. Recent updates in this version include migrating to ESM, removing the pip-install input, and upgrading the underlying @actions/cache dependency to version 6.2.0 [3]. If you are looking to control cache behavior in GitHub Actions to mitigate risks like cache poisoning, the current recommended practice remains using standard GitHub Actions caching patterns, and monitoring official repository updates for future changes to input parameters [1].

Citations:


🌐 Web query:

zizmor cache-poisoning setup-python cache pip cache-dependency-path

💡 Result:

zizmor is a static analysis tool for GitHub Actions that includes a specific audit rule called cache-poisoning [1][2]. This rule flags potential vulnerabilities where release workflows—typically triggered by events like release or push (tags)—leverage cached build states that could be manipulated by attackers to achieve unauthorized code execution during the release process [1]. When using actions/setup-python, enabling caching via cache: pip can trigger this finding because it creates a cache entry that might be persisted across different workflow runs [3][1]. Key points regarding the interaction between zizmor and actions/setup-python: 1. The Risk: An attacker with write access to the repository (e.g., via a compromised GITHUB_TOKEN or a malicious PR) could potentially poison the GitHub Actions cache [1]. If a release workflow subsequently restores and uses that poisoned cache to build or publish artifacts, it could result in supply chain compromises [3][1]. 2. Using cache-dependency-path: This input allows you to specify exactly which dependency files (e.g., requirements.txt, poetry.lock) dictate the cache key [4][5]. While cache-dependency-path is best practice for ensuring cache accuracy, it does not inherently prevent cache-poisoning [6]. 3. Remediation: To resolve zizmor cache-poisoning findings in release workflows, the primary strategy is to disable cache restoration when the workflow is running in a release context [1]. You can do this by: * Conditional Execution: Use an if condition on the step or job to skip caching when the event is a release or tag [1]. * Lookup-Only: For some actions, setting a flag like lookup-only (or equivalent) to true when a release/tag is detected can prevent the action from writing to the cache while still allowing it to look up existing entries [7]. * Disabling Caching: Simply remove caching entirely from workflows that produce official release artifacts [3][1]. If you are seeing a false positive (e.g., a release workflow that is already safely guarded), you can often suppress the finding by using appropriate conditional logic that zizmor understands, such as checking github.ref or github.event_name to explicitly disable caching during release events [7][1][8].

Citations:


Remove pip caching from the release artifact build.

cache: pip can restore dependency files whose cache key is not bound to this release commit, so the build may use poisoned cached packages before attestation/publishing. Keep caching in CI if needed, but disable it for this release workflow or enforce provenance/content hashes for cached dependency artifacts.

🧰 Tools
🪛 zizmor (1.28.0)

[error] 26-26: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): this step

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 26 - 30, Remove the cache: pip
setting from the actions/setup-python step in the release workflow, while
retaining the Python version and cache-dependency-path configuration only if
still valid without caching. Do not alter unrelated CI workflows or release
build steps.

Source: Linters/SAST tools

Comment on lines +61 to +64
permissions:
id-token: write
contents: read
attestations: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the sensitive release permissions.

Add inline rationale for id-token: write and attestations: write in the attestation job, and for id-token: write in the publish job. This makes future least-privilege reviews explicit and prevents accidental permission expansion.

As per static analysis, zizmor reported undocumented permissions at these locations.

Also applies to: 83-85

🧰 Tools
🪛 zizmor (1.28.0)

[warning] 62-62: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 61 - 64, Document the sensitive
permissions in the attestation and publish jobs of the workflow: add concise
inline rationale comments for each id-token: write and attestations: write
entry, including the publish job’s id-token: write. Keep the existing permission
values and scopes unchanged.

Source: Linters/SAST tools

Comment thread neural_mesh/consensus.py
Comment on lines +285 to +297
def __init__(
self,
providers: Sequence[Provider],
config: ConsensusConfig | None = None,
*,
usage_store: UsageStore | None = None,
) -> None:
self.config = config or ConsensusConfig()
self._providers = tuple(providers)
self._usage_store = usage_store
self._provider_by_name = _validate_providers(self._providers, self.config)
self._semaphore = asyncio.Semaphore(self.config.max_concurrency)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

asyncio Semaphore acquire lazy loop binding contended path Python 3.10 source

💡 Result:

In Python 3.10, the asyncio.Semaphore implementation manages its internal state primarily through an internal counter (self._value) and a deque of waiters (self._waiters) [1][2]. Regarding the specific mechanisms in the contended path: 1. Lazy Initialization: The _waiters deque is initialized eagerly in the init method for the standard Python 3.10 release (as seen in the relevant CPython source for that version) [1][2]. The pattern of "lazy initialization" (setting self._waiters = None and allocating it only upon the first contention) was introduced in later versions of Python (e.g., in the main development branch, GH-97545) to improve performance, but it was not present in the initial Python 3.10 stable release [3][4]. 2. Contended Path: When the semaphore is contended (i.e., self._value <= 0 or there are existing waiters), the acquire coroutine creates a new asyncio.Future, appends it to the self._waiters deque, and awaits it [1][2]. The loop continues to wait until the task is resumed. 3. Loop Binding: In Python 3.10, the loop parameter was deprecated and removed from the Semaphore constructor, meaning the semaphore binds to the running loop at the time of creation (or at the time of future creation) using asyncio.get_event_loop or similar internal mechanisms [5][1]. The internal logic relies on the loop's ability to schedule and resolve these futures [1][2]. 4. FIFO and Re-acquisition: Python 3.10 contains logic (specifically bpo-45997) designed to improve FIFO ordering and handle re-acquisition, often involving flags like _wakeup_scheduled to prevent race conditions where a task might be woken up but fail to acquire the semaphore before another task steals it [6][7]. In summary, for Python 3.10, the semaphore does not use lazy initialization for the waiter deque; it uses an eager initialization strategy, and the contended path involves managing a deque of futures that are awaited by blocked coroutines [1][2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)consensus\.py$|pyproject\.toml|uv\.lock|poetry\.lock|requirements.*\.txt' || true

echo "== consensus outline =="
ast-grep outline neural_mesh/consensus.py --view expanded | sed -n '1,220p' || true

echo "== init and run slices =="
sed -n '260,345p' neural_mesh/consensus.py | cat -n

echo "== config/semaphore references =="
rg -n "max_concurrency|Semaphore|_semaphore|asyncio\.Semaphore|acquire\(" neural_mesh tests .github 2>/dev/null || true

echo "== event loop/runtime references =="
rg -n "asyncio\.run|create_task|async with .*Semaphore|_invoke_provider" neural_mesh tests 2>/dev/null || true

echo "== python sem sample source excerpt if installed =="
python3 - <<'PY'
import inspect, asyncio
print(asyncio.__version__ if hasattr(asyncio, '__version__') else "python", __import__('sys').version)
src = inspect.getsource(asyncio.Semaphore).__splitlines = src.splitlines()
for i,line in enumerate(src[:80], 1):
    if "def _get_loop" in line or "_get_loop" in line or "create_future" in line or "Future(" in line:
        print(src[max(0,i-4):i+5])
        print("---")
PY

Repository: Deathcharge/neural-mesh

Length of output: 1932


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== behavioral probe: Semaphore reused across asyncio.run calls under contention =="
python3 - <<'PY'
import asyncio, sys, traceback

sem = asyncio.Semaphore(1)

async def first_loop():
    task = asyncio.create_task(sem.acquire())
    await asyncio.sleep(0)          # wait for loop binding/waiter future setup
    return task

async def second_loop():
    try:
        await sem.acquire()
        return "ok"
    except Exception as exc:
        return type(exc).__name__ + ": " + str(exc)

print(sys.version)
loop1 = asyncio.run(first_loop())
try:
    loop1.cancel()
    asyncio.run(loop1)
except asyncio.CancelledError:
    pass
second = asyncio.run(second_loop())
print(second)

print("\ntwo runs both acquire without contention:")
for _ in range(3):
    try:
        asyncio.run(sem.acquire())
    except RuntimeError as exc:
        print("RuntimeError:", str(exc))
PY

Repository: Deathcharge/neural-mesh

Length of output: 1932


🌐 Web query:

Python asyncio Semaphore RuntimeError is bound to a different event loop reuse across asyncio.run Semaphore acquire loop binding

💡 Result:

The RuntimeError: "is bound to a different event loop" occurs when an asyncio synchronization primitive (like asyncio.Semaphore, Lock, or Event) is instantiated outside of an active event loop, causing it to "bind" to a default or stale event loop [1][2][3]. When you later attempt to use that primitive within a new event loop—most commonly triggered by calling asyncio.run()—the primitive detects a mismatch between its bound loop and the current running loop, resulting in this error [1][2]. ### Why this happens Historically, asyncio primitives required a reference to an event loop upon initialization [1][3]. If no loop was explicitly provided, they would attempt to capture the current running loop using asyncio.get_event_loop() [1][4]. Because asyncio.run() creates a brand-new event loop instance every time it is called, any primitive initialized at the module level (before asyncio.run() is invoked) becomes incompatible with the new, ephemeral loop [1][2]. ### How to fix it The recommended solution is to ensure the Semaphore (or other primitive) is initialized only after the event loop has started [1][2]. 1. Initialize inside the entry point: Move the instantiation of the Semaphore inside your async main() function or the coroutine passed to asyncio.run() [2]. python async def main(): # Initialize inside the loop sem = asyncio.Semaphore(10) await sem.acquire() # ... asyncio.run(main()) 2. Pass the primitive as an argument: If you need to share the Semaphore across different tasks, initialize it within the main coroutine and pass it explicitly to other functions rather than relying on a global variable [2]. ### Note on Python 3.10+ In Python 3.10 and later, the loop parameter was removed from the constructors of asyncio synchronization primitives [5][6]. While this change simplified the API, the underlying requirement for primitives to be bound to a single, active event loop remains [3][7]. If you encounter this error in modern Python versions, it is almost certainly because the primitive was still instantiated before the event loop was available (e.g., at the module global scope) [2][3].

Citations:


Avoid reusing the semaphore across event loops.

self._semaphore is created once in __init__, before any run() call knows which event loop will execute it. If the same ConsensusEngine is used from separate asyncio.run() calls, its semaphore can fail when contention creates a waiting future for a different loop and raises RuntimeError: ... is bound to a different event loop. Create the semaphore inside run() so it is scoped to that call’s loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@neural_mesh/consensus.py` around lines 285 - 297, Move semaphore creation out
of ConsensusEngine.__init__ and into run(), creating a fresh asyncio.Semaphore
using self.config.max_concurrency for each invocation. Update run() and any
related helpers to use that per-call semaphore, ensuring separate asyncio.run()
event loops never share the same semaphore.

Comment thread neural_mesh/consensus.py
Comment on lines +723 to +738
def _usage_record(result: ConsensusResult) -> UsageRecord:
task_digest = hashlib.sha256(result.task.encode("utf-8")).hexdigest()
return UsageRecord(
timestamp=result.created_at,
task_sha256=task_digest,
agreement_level=result.agreement_level.value,
providers_queried=result.providers_queried,
providers_succeeded=result.providers_succeeded,
requested_max_tokens=result.requested_max_tokens,
reported_input_tokens=result.reported_input_tokens,
reported_output_tokens=result.reported_output_tokens,
token_reporting_complete=result.token_reporting_complete,
reported_cost_usd=result.reported_cost_usd,
cost_reporting_complete=result.cost_reporting_complete,
duration_ms=result.duration_ms,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Unsalted task_sha256 weakens the "privacy-minimized" guarantee for low-entropy task labels.

task_sha256 is a bare hashlib.sha256 of the caller-supplied task string. Since task is a short free-form label (validated only up to 500 chars in _validate_request), an unsalted hash is trivially reversible via a rainbow table for any predictable/guessable set of task names, which undercuts the privacy goal stated in usage.py's UsageRecord docstring. Consider a keyed HMAC (with a per-installation secret) instead of a bare hash if the intent is to prevent recovery of the original task string from the persisted record.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@neural_mesh/consensus.py` around lines 723 - 738, Update _usage_record so
task_sha256 is derived with a per-installation secret via keyed HMAC rather than
bare hashlib.sha256, using the existing configuration or secret-management
mechanism and preserving the hexadecimal digest format expected by UsageRecord.
Ensure the same stable secret is reused across records while avoiding
persistence of the raw task label.

Comment thread pyproject.toml
Comment on lines +100 to +104
[tool.ruff.lint]
select = ["B", "E", "F", "I", "RUF", "SIM", "UP"]

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dead per-file-ignore: S101 has no effect.

select only includes ["B", "E", "F", "I", "RUF", "SIM", "UP"]; the S (flake8-bandit) category is never enabled, so the tests/** ignore for S101 is a no-op. Either add "S" to select (if assert-based bandit checks are actually wanted) or drop the now-meaningless ignore entry.

♻️ Option: enable S and keep the ignore meaningful
 [tool.ruff.lint]
-select = ["B", "E", "F", "I", "RUF", "SIM", "UP"]
+select = ["B", "E", "F", "I", "RUF", "S", "SIM", "UP"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[tool.ruff.lint]
select = ["B", "E", "F", "I", "RUF", "SIM", "UP"]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]
[tool.ruff.lint]
select = ["B", "E", "F", "I", "RUF", "S", "SIM", "UP"]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` around lines 100 - 104, Update the Ruff configuration in the
[tool.ruff.lint] section so the tests/** per-file-ignore for S101 is meaningful:
either add the S category to select if Bandit checks are intended, or remove the
S101 ignore entry if they are not.

@Deathcharge
Deathcharge merged commit 0200fc6 into main Jul 29, 2026
7 checks passed
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