Skip to content

examples: add agent-action-monitor (behavioural gate for AI agent actions)#209

Open
ritiksah141 wants to merge 6 commits into
superlinked:mainfrom
ritiksah141:examples/agent-action-monitor-dusk
Open

examples: add agent-action-monitor (behavioural gate for AI agent actions)#209
ritiksah141 wants to merge 6 commits into
superlinked:mainfrom
ritiksah141:examples/agent-action-monitor-dusk

Conversation

@ritiksah141

@ritiksah141 ritiksah141 commented Jul 13, 2026

Copy link
Copy Markdown

Summary

Adds a new example at examples/agent-action-monitor/. A behavioural gate for AI agent actions, built on DUSK, an open source behavioural threat detection project. The core idea: a hijacked AI agent still holds valid credentials, so a credential check waves it through. This gate asks a different question, does this agent normally do this, by scoring a proposed control-plane action (a firewall rule change, a route change, a role grant) against that agent's own learned baseline before the action reaches a downstream system.

SIE is optional and degrades gracefully throughout the request path. Semantic novelty and qualifying extraction results can increase, but never reduce, the deterministic anomaly score.

  • extract uses GLiNER with the labels role, privilege, resource, segment, and port. Confident, previously unknown role or privilege entities contribute an additional bounded anomaly signal; neutral resource, segment, and port entities do not add score by themselves.
  • encode (BAAI/bge-m3) embeds each recorded verdict once. When sufficient history exists, it also embeds the incoming action for the bounded fleet-wide similar-decision lookup. It does not contribute directly to the anomaly score.
  • score (BAAI/bge-reranker-v2-m3) reranks the acting agent's learned baseline history to detect semantic novelty and separately reranks the fleet-wide similar-decision shortlist.

Repeat-offense memory persists refused verdicts per agent. A matching later attempt receives a bounded, decaying contribution and cites the earlier trace ID. Storage is capped at 50 offenses per agent and 500 tracked agents.

Also adds a row to examples/README.md's gallery table.

Architecture

Five services on one Docker Compose stack, no API key required by default: dusk-gate (the Flask /v1/gate service), sie (self-hosted, CPU default image), n8n (three webhooks, decision, report, alert, baked in and active from container start), mock-prod (a dummy downstream target), and agent-demo (a Bedrock or mock agent harness that runs a clean and a poisoned scenario against the gate, then exits).

Watch mode is the default: a refused action is logged as WOULD-BLOCK but still reaches mock-prod, since an inline gate that wrongly blocks a legitimate action can disrupt a network. Enforce mode upgrades WOULD-BLOCK to BLOCK, which stops the action before it reaches mock-prod.

Run it

cd examples/agent-action-monitor
docker compose up

Brings up the full stack. agent-demo runs a clean and a poisoned scenario against the gate automatically, then exits. See the example's own README for the full walkthrough, watch versus enforce mode, and the n8n webhook views.

Validated end to end

Documented in the example's own docs/sie-primitives.md, from a run against Superlinked's hosted tester cluster:

  • sie_encode returns a real 1024-dimensional dense vector from BAAI/bge-m3, confirming the model actually loaded and served
  • Precision and recall on the labelled fixture stay at 1.0 and 1.0 with SIE enabled, matching the deterministic-only baseline exactly, no regression from enabling SIE
  • At least one attack's reasons carries a real SIE rerank or SIE extract marker, confirming the primitives are actually contributing a signal over the network
  • The full test suite passes unchanged with live SIE enabled
  • Restart persistence is covered by an automated test, test_offense_memory_persists_across_a_simulated_restart, which restarts the gate engine and confirms a repeat offense is still cited afterward

Test plan

  • docker compose up starts the full stack; health-gated services become healthy and agent-demo completes both scenarios successfully
  • agent-demo's clean scenario returns ALLOW and reaches mock-prod
  • agent-demo's poisoned scenario returns WOULD-BLOCK in watch mode and still reaches mock-prod
  • Setting DUSK_ENFORCE=true on dusk-gate turns the same poisoned scenario into BLOCK, and it no longer reaches mock-prod
  • pytest passes standalone from a fresh pip install -e ".[dev]" of this directory alone, 186 passed, 2 skipped
  • ruff check, mypy, and bandit clean

Notes and caveats

  • /v1/gate is unauthenticated with CORS open to all origins by default, appropriate for a local example, not a production security boundary
  • The deterministic feature checks carry the primary anomaly scoring; the three SIE primitives are an enrichment layer on top, not a replacement, so the gate's core detection logic never depends on any AI model at runtime
  • This example covers only the agent-action gate. DUSK's separate network and packet detection layer stays in the main DUSK repository and is out of scope here

Built by Ritik Sah and Tanvir Farhad.

Summary by CodeRabbit

  • New Features
    • Added a runnable agent-action-monitor example with watch/enforce verdicts for proposed actions, local gate HTTP API, similarity-based decision context, and durable offense memory with deterministic fallback behavior.
    • Shipped Azure and Bedrock ingestion adapters plus an n8n webhook delivery flow and local mock downstream service.
    • Included Dockerfiles, compose stack, and safer example build/run settings (.dockerignore, sample environment).
  • Documentation
    • Added/expanded example documentation, sample action data, API contract (OpenAPI), and schema/signal explanations.
  • Tests
    • Added comprehensive unit and integration coverage, including gate API behavior, adapters, persistence durability, configuration validation, webhook payloads, and load/performance helpers.

…ions)

Adds a new example at examples/agent-action-monitor/. A behavioural gate
for AI agent actions, built on DUSK, an open source behavioural threat
detection project. A hijacked AI agent still holds valid credentials, so
a credential check waves it through. This gate asks a different
question, does this agent normally do this, by scoring a proposed
control-plane action against that agent's own learned baseline before
the action reaches a downstream system.

All three SIE primitives are wired into the live /v1/gate request path:
extract (GLiNER) for privileged terms, encode (bge-m3) for a bounded
fleet-wide similar-decision lookup, and score (bge-reranker-v2-m3) for
semantic novelty against the agent's own baseline history. SIE is
optional and degrades gracefully throughout the request path.

Also adds a row to examples/README.md's gallery table.

Built by Ritik Sah and Tanvir Farhad.
@TFT444

TFT444 commented Jul 13, 2026

Copy link
Copy Markdown

Hi @svonava,Quick update load testing is done, the latency table is in the README, and CI is green.

This is ready for review whenever you get a chance.

Thanks again for the support.

…on-monitor-dusk

# Conflicts:
#	examples/README.md

@fm1320 fm1320 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for this contribution, and for the care that went into it: the pins all check out, the test suite is real, and the additive-only SIE design holds in the code. A few things before we can merge.

Main blocker: the example needs to show SIE earning its place. The benchmark can't show SIE lift by construction: the negatives are the training set and the 3 attacks are built to trip the static rules, so precision/recall are 1.0/1.0 with SIE on or off. You already built the mechanism to fix this (test_sie_extract_flags_terms_missed_by_the_static_frozenset): please add held-out negatives and at least one attack that evades the deterministic rules and is caught only via extract or rerank novelty, so disabling SIE visibly drops recall. Until then, please remove "evidence that SIE is load-bearing" (sie-primitives.md) and "disabling SIE degrades detection quality" (README).

Out-of-the-box fixes:

  • .env is dead config: compose has no ${VAR} interpolation or env_file, and SIE_API_KEY never reaches the gate container.
  • The real-Bedrock path can't work (hardcoded USE_REAL_BEDROCK: "false", no AWS creds plumbed, model ID needs the us. inference-profile prefix). Wire it or document it as mock-only.
  • Document cold start: ~5.7 GB weight download against a ~150s healthcheck window and ~7 GB RSS for the sie container. On slow connections or default Docker Desktop RAM, compose up aborts with no hint why. Note the Apple Silicon emulation penalty too.
  • The without-Docker flow is broken: gate defaults to port 5000, harness targets 8000, and requests is only in the dev extra.
  • n8n: N8N_USER_MANAGEMENT_DISABLED is a no-op since 1.0 (UI hits an owner-setup screen); please set N8N_DIAGNOSTICS_ENABLED: "false" since the compose header claims nothing phones home.

Engine fixes:

  • Repeat-offense refusals caused only by the repeat boost are re-recorded with fresh timestamps, so the documented decay never happens and watch mode alters future scoring. Record based on the score excluding the repeat contribution.
  • OffenseRecord.from_dict accepts naive timestamps; _decay then raises on every evaluation for that agent. Validate like AgentAction does.
  • _load_gate_engine misses OSError (e.g. a mis-mounted volume), turning intended 503 degradation into per-request 500s.
  • A half-up SIE can stall one gate call ~50s (five sequential calls at 10s timeout). A short per-call timeout or circuit breaker on the inline path would fix it.
  • agent_id accepts newlines, allowing forged lines in the gate's own audit log. Reject control characters.
  • Caps bound counts, not bytes: large unique-token targets and rotating agent IDs blow past the advertised bounds and can LRU-evict a real agent's offense history.

Scope: at 71 files this is several times larger than our biggest existing example, and most of it is DUSK product code rather than SIE integration. We'd genuinely consider a version where the engine lives in your repo and this example is the thin integration layer. Open to discussing either shape.

Also, the branch conflicts with main, so please rebase so CI can run. Happy to re-review quickly once these land: with a benchmark where SIE visibly carries signal, this becomes a strong example.

…on-monitor-dusk

# Conflicts:
#	examples/README.md
Rebuilds the live SIE benchmark so it actually shows SIE is load bearing:
adds held out negatives the baseline never trained on, and an attack built
to evade every deterministic check, then scores it with SIE live versus
forced off to show the score lands on opposite sides of the block
threshold, not just that a reason string mentions SIE.

Also fixes:
- SIE_API_KEY and AWS credentials are wired through compose.yml instead of
  being dead config
- the real Bedrock model id now uses the region-prefixed inference profile
  id Bedrock requires for on-demand invocation
- documented the actual cold start cost (weight download size, healthcheck
  window, RSS, Apple Silicon emulation)
- the without-Docker flow now installs agent-demo/mock-prod's own
  requirements and the gate's default port matches everywhere else
- swapped the no-op N8N_USER_MANAGEMENT_DISABLED for N8N_DIAGNOSTICS_ENABLED
- a refusal driven only by the repeat-offense boost no longer re-records
  itself with a fresh timestamp and defeats its own decay
- OffenseRecord rejects naive timestamps instead of crashing decay later
- the gate engine loader catches OSError broadly, not just FileNotFoundError
- an in-process circuit breaker bounds how long a half-up SIE can stall a
  single gate request
- agent_id rejects control characters
- offense memory bounds bytes, not just record and agent counts

Also merges upstream/main to resolve the gallery table conflict in
examples/README.md.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c59599a-b162-42fe-afb2-cc77b20192da

📥 Commits

Reviewing files that changed from the base of the PR and between 0a4a98f and 4121770.

📒 Files selected for processing (1)
  • examples/agent-action-monitor/src/dusk/actions/baseline.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/agent-action-monitor/src/dusk/actions/baseline.py

📝 Walkthrough

Walkthrough

The pull request adds a runnable DUSK agent-action-monitor example with canonical action ingestion, behavioral baseline analysis, SIE-assisted detection, offense persistence, an HTTP gate API, Bedrock scenarios, downstream mocks, n8n webhooks, container orchestration, and extensive tests and documentation.

Changes

Agent action behavioral gate

Layer / File(s) Summary
Action contracts and ingestion
examples/agent-action-monitor/src/dusk/actions/*, contracts/*, sample-data/*, lab/actions/*
Adds canonical action schemas, source adapters, normalization, ingestion, configuration, generated fixtures, package exports, and the OpenAPI contract.
Baseline analysis and offense memory
examples/agent-action-monitor/src/dusk/actions/baseline.py, analyse.py, offense_memory.py, verdict.py, trace/vector.py
Adds deterministic behavioral scoring, SIE enrichment, repeat-offense tracking, bounded persistence, and allow/watch/block verdicts.
Gate API and observability
examples/agent-action-monitor/src/dusk/api.py, src/dusk/trace/*, compose.yml
Adds the HTTP gate, decision history, trace records, health reporting, asynchronous webhooks, and service wiring.
Agent and downstream demo
examples/agent-action-monitor/agent-demo/*, mock-prod/*, n8n/*
Adds clean and poisoned Bedrock scenarios, gate enforcement flow, mock production application, n8n webhook endpoints, and container definitions.
Validation and documentation
examples/agent-action-monitor/tests/*, README.md, docs/*, pyproject.toml
Adds unit/integration tests, live SIE benchmarks, run instructions, schema and latency documentation, and development tooling configuration.

Sequence Diagram(s)

sequenceDiagram
  participant AgentDemo
  participant Bedrock
  participant DuskGate
  participant N8n
  participant MockProd
  AgentDemo->>Bedrock: converse tool-use request
  Bedrock-->>AgentDemo: proposed action
  AgentDemo->>DuskGate: POST /v1/gate
  DuskGate->>N8n: decision/report/alert webhook
  DuskGate-->>AgentDemo: ALLOW, WOULD-BLOCK, or BLOCK
  AgentDemo->>MockProd: POST /apply for non-blocked verdicts
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.09% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the new agent-action-monitor example and its purpose as a behavioural gate for AI agent actions.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 13

🧹 Nitpick comments (4)
examples/agent-action-monitor/tests/test_actions_offense_memory.py (1)

144-167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Sleep-based scheduling assumptions risk CI flakiness. Both tests use a fixed time.sleep() duration to assume a background thread has reached a particular point before asserting on shared state, which can intermittently fail under CI contention.

  • examples/agent-action-monitor/tests/test_actions_offense_memory.py#L144-L167: the elapsed < 0.2 bound in test_record_does_not_block_on_a_slow_disk_write has only the margin between "instant" and the 0.2s patched sleep; consider widening the patched sleep (e.g. 1s+) relative to the assertion threshold, or asserting via a signal (e.g. an event set just before the sleep) rather than wall-clock elapsed time.
  • examples/agent-action-monitor/tests/test_n8n_client.py#L125-L163: time.sleep(0.1) "to let the single worker pick up the first send" is a guess at scheduling latency; consider polling _pending_count with a timeout, or have _blocking_send signal an event once entered, instead of a fixed sleep before asserting the backlog bound.
🤖 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 `@examples/agent-action-monitor/tests/test_actions_offense_memory.py` around
lines 144 - 167, The tests rely on fixed sleep durations for background-thread
scheduling and can be flaky under CI contention. In
examples/agent-action-monitor/tests/test_actions_offense_memory.py:144-167,
update test_record_does_not_block_on_a_slow_disk_write to use a synchronization
event before the delayed Path.replace call, or widen the delay substantially
relative to the elapsed-time assertion. In
examples/agent-action-monitor/tests/test_n8n_client.py:125-163, update the test
around _blocking_send to signal when the worker enters the first send and wait
for that signal, or poll _pending_count with a timeout, instead of sleeping for
a fixed duration before asserting the backlog bound.
examples/agent-action-monitor/tests/test_actions_gate.py (1)

267-268: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate assertion.

Line 268 repeats line 267 verbatim; safe to drop the duplicate.

🧹 Proposed fix
     assert not any("SIE extract" in r for r in extracted.reasons)
-    assert not any("SIE extract" in r for r in extracted.reasons)
🤖 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 `@examples/agent-action-monitor/tests/test_actions_gate.py` around lines 267 -
268, Remove the duplicate identical assertion in the test around
extracted.reasons, leaving a single check that no reason contains “SIE extract.”
examples/agent-action-monitor/contracts/gate.openapi.yaml (1)

1-99: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

No authentication scheme defined in the contract.

The spec defines no security requirement for POST /v1/gate, consistent with the implementation in api.py having no auth check. See the consolidated comment on api.py for the shared concern.

🤖 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 `@examples/agent-action-monitor/contracts/gate.openapi.yaml` around lines 1 -
99, The OpenAPI contract leaves POST /v1/gate unauthenticated, matching the
current api.py implementation. Preserve this behavior by keeping the operation
without a security requirement and do not add an authentication scheme or auth
enforcement to the contract.
examples/agent-action-monitor/src/dusk/actions/ingest.py (1)

56-63: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Widen the per-record exception guard.

Only AdapterError is caught per record. If an adapter has a latent bug and raises something else (KeyError, AttributeError, etc.) for one malformed record, it escapes ingest_file entirely and aborts the whole batch — undermining the file's own "one bad record is the caller's to skip" design (as documented in adapters/base.py). _load_gate_engine() in api.py only catches (OSError, ValueError), so this would also surface unhandled up through the lazy gate-engine loader.

🛡️ Proposed fix
         try:
             actions.append(normalise_record(source, record))
-        except AdapterError as exc:
+        except (AdapterError, Exception) as exc:  # noqa: BLE001
             logger.warning("Skipping record %d in %s: %s", index, path, exc)
🤖 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 `@examples/agent-action-monitor/src/dusk/actions/ingest.py` around lines 56 -
63, Widen the per-record exception handler in ingest_file around
normalise_record so any unexpected Exception is logged and skipped for that
record, while preserving the existing AdapterError warning context and batch
processing behavior. Do not catch BaseException subclasses such as interrupts or
system exits.
🤖 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 `@examples/agent-action-monitor/agent-demo/bedrock_client.py`:
- Around line 17-20: Update the verdict reason handling in the exception class
__init__ to treat an explicitly null reasons value like a missing value before
joining it. Preserve the existing comma-joined message for provided reason lists
and the “no reason given” fallback when reasons is absent or null.

In `@examples/agent-action-monitor/agent-demo/harness.py`:
- Around line 84-93: Update run_scenario_or_raise to raise DuskBlockedError only
for a real BLOCK verdict, while returning the result for WOULD-BLOCK so callers
preserve its applied action outcome. Keep ALLOW and NO_ACTION behavior
unchanged, and add coverage in test_harness.py for the WOULD-BLOCK
exception-based flow.

In `@examples/agent-action-monitor/compose.yml`:
- Around line 1-2: Update the compose configuration’s DUSK_ENFORCE environment
handling so values from .env can enable enforce mode instead of being overridden
by a hard-coded value. Preserve the documented default behavior when
DUSK_ENFORCE is unset, while forwarding the resolved setting to the container.

In `@examples/agent-action-monitor/docs/gate-latency-notes.md`:
- Around line 97-100: Update the watch-mode delivery statement in the
correctness summary to reflect that WOULD-BLOCK actions are forwarded, including
to /apply, and may be applied. Remove the inaccurate claim that poisoned actions
were never applied while preserving the accurate ALLOW delivery details.

In `@examples/agent-action-monitor/docs/sie-primitives.md`:
- Around line 21-27: The SIE request-path documentation incorrectly excludes
sie_encode from /v1/gate. Update
examples/agent-action-monitor/docs/gate-latency-notes.md lines 73-75 to describe
the _find_similar_decisions() and _record_decision() flow, including
embed_text()/sie_encode, and keep
examples/agent-action-monitor/docs/sie-primitives.md lines 21-27 aligned with
that behavior; both sites require documentation updates.

In `@examples/agent-action-monitor/scripts/vulture_whitelist.py`:
- Around line 25-29: Update the SimilarDecision.similarity entry in
vulture_whitelist.py to resolve similarity through an instance rather than a
class-level attribute, using a construct that is safe to evaluate during module
import and preserves the whitelist’s purpose.

In `@examples/agent-action-monitor/src/dusk/actions/baseline.py`:
- Around line 26-42: Update target_tokens and _flatten_scalars to enforce a
request-driven width limit in addition to the existing recursion depth limit.
Track the number of scanned nodes or materialized tokens with a shared cap, stop
traversal before the cap is exceeded, and preserve the current lowercase scalar
extraction behavior for inputs within the limit.

In `@examples/agent-action-monitor/src/dusk/actions/event.py`:
- Around line 74-75: Update the target validation in the event action’s
validation method to reject control characters using the same check already
applied to agent_id. Preserve the existing non-empty-string validation and raise
the established validation error for invalid target values.

In `@examples/agent-action-monitor/src/dusk/api.py`:
- Around line 18-21: Restrict CORS in the Flask app initialization so it is not
wildcard-enabled across every route, and add authentication checks to the
/v1/gate endpoint, preserving /health only if it is intentionally public. Update
the gate API contract alongside the implementation to document the required
security scheme, using the existing app configuration and route definitions as
the integration points.

In `@examples/agent-action-monitor/src/dusk/config.py`:
- Line 102: Constrain the score-like configuration thresholds, including
gate_block_threshold and the related settings in the configuration definitions
around them, to the inclusive range [0, 1]. Ensure values above 1.0 or below 0.0
raise the existing ConfigError during configuration validation rather than
allowing the gate to silently degrade.

In `@examples/agent-action-monitor/src/dusk/trace/n8n_client.py`:
- Around line 24-30: Synchronize lazy initialization in _get_executor by adding
the module’s existing locking pattern around the _executor check-and-set,
matching the double-checked locking used for _gate_engine. Ensure concurrent
first calls create and retain only one ThreadPoolExecutor while subsequent calls
continue returning the shared instance.

In `@examples/agent-action-monitor/src/dusk/trace/vector.py`:
- Around line 181-190: Move _record_sie_success() in both sie_score and
sie_extract so it executes only after response entries are fully parsed and the
result is successfully constructed. Remove the current success calls immediately
after fetching the raw payload, preserving the existing exception path where
malformed entries call _record_sie_failure() and return None.
- Around line 107-123: The SIE integration creates an unclosed client for every
call. Update _sie_client, sie_encode, sie_score, and sie_extract to reuse one
cached SIEClient instance, invalidate that cache when configuration reloads, and
close the client during reset/shutdown using its documented
cleanup/context-manager behavior.

---

Nitpick comments:
In `@examples/agent-action-monitor/contracts/gate.openapi.yaml`:
- Around line 1-99: The OpenAPI contract leaves POST /v1/gate unauthenticated,
matching the current api.py implementation. Preserve this behavior by keeping
the operation without a security requirement and do not add an authentication
scheme or auth enforcement to the contract.

In `@examples/agent-action-monitor/src/dusk/actions/ingest.py`:
- Around line 56-63: Widen the per-record exception handler in ingest_file
around normalise_record so any unexpected Exception is logged and skipped for
that record, while preserving the existing AdapterError warning context and
batch processing behavior. Do not catch BaseException subclasses such as
interrupts or system exits.

In `@examples/agent-action-monitor/tests/test_actions_gate.py`:
- Around line 267-268: Remove the duplicate identical assertion in the test
around extracted.reasons, leaving a single check that no reason contains “SIE
extract.”

In `@examples/agent-action-monitor/tests/test_actions_offense_memory.py`:
- Around line 144-167: The tests rely on fixed sleep durations for
background-thread scheduling and can be flaky under CI contention. In
examples/agent-action-monitor/tests/test_actions_offense_memory.py:144-167,
update test_record_does_not_block_on_a_slow_disk_write to use a synchronization
event before the delayed Path.replace call, or widen the delay substantially
relative to the elapsed-time assertion. In
examples/agent-action-monitor/tests/test_n8n_client.py:125-163, update the test
around _blocking_send to signal when the worker enters the first send and wait
for that signal, or poll _pending_count with a timeout, instead of sleeping for
a fixed duration before asserting the backlog bound.
🪄 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: CHILL

Plan: Pro Plus

Run ID: c7df84b7-d5a2-4178-b54d-855f0e458ead

📥 Commits

Reviewing files that changed from the base of the PR and between 2ac128c and e476291.

⛔ Files ignored due to path filters (1)
  • examples/agent-action-monitor/docs/architecture.svg is excluded by !**/*.svg
📒 Files selected for processing (70)
  • examples/README.md
  • examples/agent-action-monitor/.dockerignore
  • examples/agent-action-monitor/.env.example
  • examples/agent-action-monitor/.gitignore
  • examples/agent-action-monitor/Dockerfile
  • examples/agent-action-monitor/README.md
  • examples/agent-action-monitor/agent-demo/Dockerfile
  • examples/agent-action-monitor/agent-demo/bedrock_client.py
  • examples/agent-action-monitor/agent-demo/harness.py
  • examples/agent-action-monitor/agent-demo/load_driver.py
  • examples/agent-action-monitor/agent-demo/mock_bedrock.py
  • examples/agent-action-monitor/agent-demo/requirements.txt
  • examples/agent-action-monitor/agent-demo/run_scenario.py
  • examples/agent-action-monitor/agent-demo/stub_gate.py
  • examples/agent-action-monitor/agent-demo/test_bedrock_client.py
  • examples/agent-action-monitor/agent-demo/test_harness.py
  • examples/agent-action-monitor/agent-demo/test_load_driver.py
  • examples/agent-action-monitor/agent-demo/test_mock_bedrock.py
  • examples/agent-action-monitor/agent-demo/test_stub_gate.py
  • examples/agent-action-monitor/compose.yml
  • examples/agent-action-monitor/contracts/gate.openapi.yaml
  • examples/agent-action-monitor/docs/action-schema.md
  • examples/agent-action-monitor/docs/gate-latency-notes.md
  • examples/agent-action-monitor/docs/sie-primitives.md
  • examples/agent-action-monitor/lab/actions/generate_actions.py
  • examples/agent-action-monitor/mock-prod/Dockerfile
  • examples/agent-action-monitor/mock-prod/app.py
  • examples/agent-action-monitor/mock-prod/requirements.txt
  • examples/agent-action-monitor/mock-prod/test_app.py
  • examples/agent-action-monitor/n8n/Dockerfile
  • examples/agent-action-monitor/n8n/docker-entrypoint.sh
  • examples/agent-action-monitor/n8n/dusk-webhooks.json
  • examples/agent-action-monitor/pyproject.toml
  • examples/agent-action-monitor/sample-data/baseline.json
  • examples/agent-action-monitor/sample-data/check-mixed.json
  • examples/agent-action-monitor/scripts/vulture_whitelist.py
  • examples/agent-action-monitor/src/dusk/__init__.py
  • examples/agent-action-monitor/src/dusk/actions/__init__.py
  • examples/agent-action-monitor/src/dusk/actions/adapters/__init__.py
  • examples/agent-action-monitor/src/dusk/actions/adapters/azure.py
  • examples/agent-action-monitor/src/dusk/actions/adapters/base.py
  • examples/agent-action-monitor/src/dusk/actions/adapters/bedrock.py
  • examples/agent-action-monitor/src/dusk/actions/adapters/generic.py
  • examples/agent-action-monitor/src/dusk/actions/analyse.py
  • examples/agent-action-monitor/src/dusk/actions/baseline.py
  • examples/agent-action-monitor/src/dusk/actions/event.py
  • examples/agent-action-monitor/src/dusk/actions/ingest.py
  • examples/agent-action-monitor/src/dusk/actions/normaliser.py
  • examples/agent-action-monitor/src/dusk/actions/offense_memory.py
  • examples/agent-action-monitor/src/dusk/actions/verdict.py
  • examples/agent-action-monitor/src/dusk/api.py
  • examples/agent-action-monitor/src/dusk/config.py
  • examples/agent-action-monitor/src/dusk/trace/__init__.py
  • examples/agent-action-monitor/src/dusk/trace/models.py
  • examples/agent-action-monitor/src/dusk/trace/n8n_client.py
  • examples/agent-action-monitor/src/dusk/trace/vector.py
  • examples/agent-action-monitor/tests/conftest.py
  • examples/agent-action-monitor/tests/integration/__init__.py
  • examples/agent-action-monitor/tests/integration/test_gate_api.py
  • examples/agent-action-monitor/tests/test_actions_azure.py
  • examples/agent-action-monitor/tests/test_actions_bedrock.py
  • examples/agent-action-monitor/tests/test_actions_event.py
  • examples/agent-action-monitor/tests/test_actions_gate.py
  • examples/agent-action-monitor/tests/test_actions_generic.py
  • examples/agent-action-monitor/tests/test_actions_ingest.py
  • examples/agent-action-monitor/tests/test_actions_offense_memory.py
  • examples/agent-action-monitor/tests/test_config.py
  • examples/agent-action-monitor/tests/test_n8n_client.py
  • examples/agent-action-monitor/tests/test_sie_live_benchmark.py
  • examples/agent-action-monitor/tests/test_trace_vector.py

Comment thread examples/agent-action-monitor/agent-demo/bedrock_client.py
Comment thread examples/agent-action-monitor/agent-demo/harness.py
Comment thread examples/agent-action-monitor/compose.yml
Comment on lines +97 to +100
Correctness held throughout: every `ALLOW` reached `mock-prod` (confirmed
via its `/log`, 46 applied actions across this run and earlier manual
checks) and every poisoned action was `WOULD-BLOCK` in watch mode, never
applied.

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 | 🟡 Minor | ⚡ Quick win

Correct the watch-mode delivery claim.

WOULD-BLOCK is intentionally forwarded in watch mode, including to /apply; therefore poisoned actions were not “never applied” in this run.

🤖 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 `@examples/agent-action-monitor/docs/gate-latency-notes.md` around lines 97 -
100, Update the watch-mode delivery statement in the correctness summary to
reflect that WOULD-BLOCK actions are forwarded, including to /apply, and may be
applied. Remove the inaccurate claim that poisoned actions were never applied
while preserving the accurate ALLOW delivery details.

Comment on lines +21 to +27
- **encode** -- `src/dusk/trace/vector.py`'s `sie_encode()` (wrapped by
`embed_text()`), called live by `/v1/gate` (`src/dusk/api.py`): once per
request to embed the incoming action, and once per verdict to record it
for future lookups. `find_similar_cached()` compares the fresh query
embedding against a bounded, pre-embedded history (capped at 200 entries)
to populate the response's `similar_decision_ids`, without re-embedding
that history on every call.

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

ast-grep outline examples/agent-action-monitor/src/dusk/api.py --items all
ast-grep outline examples/agent-action-monitor/src/dusk/trace/vector.py --items all
rg -n -C3 'sie_encode|embed_text|sie_score|sie_extract|find_similar_cached' \
  examples/agent-action-monitor/src/dusk/api.py \
  examples/agent-action-monitor/src/dusk/trace/vector.py

Repository: superlinked/sie

Length of output: 13541


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== api.py relevant sections =="
sed -n '90,185p' examples/agent-action-monitor/src/dusk/api.py
sed -n '185,215p' examples/agent-action-monitor/src/dusk/api.py

echo
echo "== vector.py relevant sections =="
sed -n '125,152p' examples/agent-action-monitor/src/dusk/trace/vector.py
sed -n '340,385p' examples/agent-action-monitor/src/dusk/trace/vector.py

echo
echo "== doc relevant sections =="
sed -n '1,35p' examples/agent-action-monitor/docs/sie-primitives.md
sed -n '60,82p' examples/agent-action-monitor/docs/gate-latency-notes.md

echo
echo "== call-path probe =="
python3 - <<'PY'
from pathlib import Path
import ast

api_path = Path("examples/agent-action-monitor/src/dusk/api.py")
vec_path = Path("examples/agent-action-monitor/src/dusk/trace/vector.py")

api_tree = ast.parse(api_path.read_text())
vec_tree = ast.parse(vec_path.read_text())

imported_by = {}
for node in ast.walk(vec_tree):
    if isinstance(node, ast.FunctionDef) and node.name == "embed_text":
        for stmt in node.body:
            if isinstance(stmt, ast.Return) and isinstance(stmt.value, ast.BinOp):
                imported_by["embed_text"] = stmt.value.left

for node in ast.walk(api_tree):
    if isinstance(node, ast.FunctionDef):
        body = ast.unparse(node)
        for sym in ["evaluate_gate_action", "_find_similar_decisions", "_record_decision"]:
            if node.name == sym:
                print(f"{node.name}:")
                print("  calls _find_similar_decisions:", "_find_similar_decisions" in body)
                print("  calls _record_decision:", "_record_decision(" in body)
                print("  imports embed_text:", "from dusk.trace.vector import embed_text" in body)
                print("  imports find_similar_cached:", "find_similar_cached" in body)
                print("  calls embed_text:", "embed_text(" in body)
                print("  calls find_similar_cached:", "find_similar_cached(" in body)

print("embed_text implementation calls:", ast.unparse(imported_by.get("embed_text")))
PY

Repository: superlinked/sie

Length of output: 11392


Align the SIE request-path notes with the encode call.

/v1/gate routes through _find_similar_decisions() and _record_decision(), and _record_decision() calls embed_text() before appending the pre-embedded decision. Update examples/agent-action-monitor/docs/gate-latency-notes.md#L73-L75 so it no longer says /v1/dusk/gate only calls sie_score/sie_extract, and that sie_encode is “not on this request path.”

📍 Affects 2 files
  • examples/agent-action-monitor/docs/sie-primitives.md#L21-L27 (this comment)
  • examples/agent-action-monitor/docs/gate-latency-notes.md#L73-L75
🤖 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 `@examples/agent-action-monitor/docs/sie-primitives.md` around lines 21 - 27,
The SIE request-path documentation incorrectly excludes sie_encode from
/v1/gate. Update examples/agent-action-monitor/docs/gate-latency-notes.md lines
73-75 to describe the _find_similar_decisions() and _record_decision() flow,
including embed_text()/sie_encode, and keep
examples/agent-action-monitor/docs/sie-primitives.md lines 21-27 aligned with
that behavior; both sites require documentation updates.

Comment on lines +18 to +21
app = Flask(__name__)
CORS(app)
# Bound public input without constraining normal AgentAction payloads.
app.config["MAX_CONTENT_LENGTH"] = 1 * 1024 * 1024

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

No authentication and permissive default CORS on the gate endpoint.

CORS(app) with no resources=/origins= restricts nothing — flask-cors applies a wildcard-origin policy to every route by default. Combined with no auth check anywhere on /v1/gate or /health, any reachable client (including cross-origin browser requests) can query verdicts or flood the gate. For a service whose stated purpose is gating privileged control-plane actions, this is worth locking down before any non-local deployment. See the consolidated note (shared with contracts/gate.openapi.yaml, which also documents no security scheme).

🤖 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 `@examples/agent-action-monitor/src/dusk/api.py` around lines 18 - 21, Restrict
CORS in the Flask app initialization so it is not wildcard-enabled across every
route, and add authentication checks to the /v1/gate endpoint, preserving
/health only if it is intentionally public. Update the gate API contract
alongside the implementation to document the required security scheme, using the
existing app configuration and route definitions as the integration points.

Comment thread examples/agent-action-monitor/src/dusk/config.py
Comment thread examples/agent-action-monitor/src/dusk/trace/n8n_client.py
Comment thread examples/agent-action-monitor/src/dusk/trace/vector.py
Comment on lines +181 to +190
entries = result["scores"] if isinstance(result, dict) else getattr(result, "scores", None)
_record_sie_success()
if not entries:
return None
score_by_id = {str(e["item_id"]): _sigmoid(float(e["score"])) for e in entries}
return [score_by_id.get(str(i), 0.0) for i in range(len(candidates))]
except Exception as exc: # noqa: BLE001
_record_sie_failure()
logger.warning("SIE score failed: %s", exc)
return None

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 | 🟡 Minor | ⚡ Quick win

Success recorded before response parsing finishes.

In both sie_score and sie_extract, _record_sie_success() fires right after the raw payload is fetched but before its entries are parsed. If a subsequent malformed entry (missing "item_id"/"text" key) raises, it's caught by the same except Exception block and calls _record_sie_failure() — recording both a success and a failure for the one call, which skews the circuit breaker's failure count.

Move _record_sie_success() to after parsing completes successfully.

Also applies to: 220-246

🤖 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 `@examples/agent-action-monitor/src/dusk/trace/vector.py` around lines 181 -
190, Move _record_sie_success() in both sie_score and sie_extract so it executes
only after response entries are fully parsed and the result is successfully
constructed. Remove the current success calls immediately after fetching the raw
payload, preserving the existing exception path where malformed entries call
_record_sie_failure() and return None.

…xample

Fixes bugs introduced by the previous commit:
- event.py's target field was missing the control-character check its own
  docstring claimed it had
- vector.py recorded a SIE call as successful before its response finished
  parsing, so a malformed entry double counted as both a success and a
  failure and skewed the circuit breaker
- vulture_whitelist.py referenced a dataclass field with no defaults
  through the class instead of an instance, which raises AttributeError if
  the file is ever actually executed

Also addresses pre-existing findings surfaced by the same review pass:
- run_scenario_or_raise no longer raises for WOULD-BLOCK, since watch mode
  already applied the action and raising hid that from the caller
- DuskBlockedError falls back correctly when reasons is explicitly None,
  not only when the key is missing
- DUSK_ENFORCE is now overridable through .env like the other compose vars
- gate_block_threshold is now bounded to <= 1.0, since analyse.py always
  caps the score to 1.0 and a threshold above it would silently make the
  gate unable to refuse anything
- the n8n webhook executor's lazy singleton now uses the same
  double-checked locking as the gate engine's, closing a race where two
  concurrent first calls could each build their own executor
- feature extraction now bounds traversal width, not just recursion depth
- ingest_file skips any malformed record, not only ones raising
  AdapterError, matching its own documented one-bad-record contract
- the SIE client is now cached per (endpoint, api key, timeout) instead of
  rebuilt on every single call, and closed when the cache is invalidated
- a duplicate assertion and two sleep-based flaky test patterns are cleaned
  up in favour of explicit synchronization

Corrects gate-latency-notes.md, which claimed sie_encode was not on the
/v1/gate request path; it is, via _find_similar_decisions/_record_decision.

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

♻️ Duplicate comments (1)
examples/agent-action-monitor/src/dusk/actions/baseline.py (1)

35-36: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Make the extraction bounds limit work, not only retained output.

  • examples/agent-action-monitor/src/dusk/actions/baseline.py#L35-L36: findall() builds every match before slicing to _MAX_TOKENS, so repetitive targets can still allocate an unbounded match list. Use lazy finditer() with islice() or an explicitly capped loop.
  • examples/agent-action-monitor/src/dusk/actions/baseline.py#L52-L60: once budget reaches zero, the enclosing dict/list loops still enumerate remaining items. Also, depth-rejected children do not consume budget, allowing wide containers at the depth boundary to be fully scanned. Decrement before the depth check and break parent loops when the budget is exhausted.
Proposed fix
+from itertools import islice
+
-    return set(_TOKEN_RE.findall(target.lower())[:_MAX_TOKENS])
+    return {
+        match.group(0)
+        for match in islice(_TOKEN_RE.finditer(target.lower()), _MAX_TOKENS)
+    }

-    if _depth > 10 or budget[0] <= 0:
+    if budget[0] <= 0:
         return
     budget[0] -= 1
+    if _depth > 10:
+        return
     if isinstance(payload, dict):
         for value in payload.values():
+            if budget[0] <= 0:
+                break
             _flatten_scalars(value, values, budget, _depth=_depth + 1)
     elif isinstance(payload, list):
         for value in payload:
+            if budget[0] <= 0:
+                break
             _flatten_scalars(value, values, budget, _depth=_depth + 1)
🤖 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 `@examples/agent-action-monitor/src/dusk/actions/baseline.py` around lines 35 -
36, The token extraction near baseline.py lines 35-36 must cap regex work, not
just the resulting set: replace findall slicing with lazy finditer consumption
limited to _MAX_TOKENS. In the container traversal near lines 52-60, decrement
budget before checking depth, and break enclosing dict/list loops as soon as
budget reaches zero; apply these changes at both cited sites in the same file.
🤖 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.

Duplicate comments:
In `@examples/agent-action-monitor/src/dusk/actions/baseline.py`:
- Around line 35-36: The token extraction near baseline.py lines 35-36 must cap
regex work, not just the resulting set: replace findall slicing with lazy
finditer consumption limited to _MAX_TOKENS. In the container traversal near
lines 52-60, decrement budget before checking depth, and break enclosing
dict/list loops as soon as budget reaches zero; apply these changes at both
cited sites in the same file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b7ff88d0-9b25-4f9e-a736-14119b8290d1

📥 Commits

Reviewing files that changed from the base of the PR and between e476291 and 0a4a98f.

📒 Files selected for processing (17)
  • examples/agent-action-monitor/agent-demo/bedrock_client.py
  • examples/agent-action-monitor/agent-demo/harness.py
  • examples/agent-action-monitor/agent-demo/test_harness.py
  • examples/agent-action-monitor/compose.yml
  • examples/agent-action-monitor/docs/gate-latency-notes.md
  • examples/agent-action-monitor/scripts/vulture_whitelist.py
  • examples/agent-action-monitor/src/dusk/actions/baseline.py
  • examples/agent-action-monitor/src/dusk/actions/event.py
  • examples/agent-action-monitor/src/dusk/actions/ingest.py
  • examples/agent-action-monitor/src/dusk/config.py
  • examples/agent-action-monitor/src/dusk/trace/n8n_client.py
  • examples/agent-action-monitor/src/dusk/trace/vector.py
  • examples/agent-action-monitor/tests/conftest.py
  • examples/agent-action-monitor/tests/test_actions_gate.py
  • examples/agent-action-monitor/tests/test_actions_offense_memory.py
  • examples/agent-action-monitor/tests/test_config.py
  • examples/agent-action-monitor/tests/test_n8n_client.py
💤 Files with no reviewable changes (1)
  • examples/agent-action-monitor/tests/test_actions_gate.py
🚧 Files skipped from review as they are similar to previous changes (14)
  • examples/agent-action-monitor/scripts/vulture_whitelist.py
  • examples/agent-action-monitor/src/dusk/actions/event.py
  • examples/agent-action-monitor/compose.yml
  • examples/agent-action-monitor/src/dusk/trace/n8n_client.py
  • examples/agent-action-monitor/agent-demo/harness.py
  • examples/agent-action-monitor/src/dusk/actions/ingest.py
  • examples/agent-action-monitor/tests/test_config.py
  • examples/agent-action-monitor/agent-demo/bedrock_client.py
  • examples/agent-action-monitor/tests/test_actions_offense_memory.py
  • examples/agent-action-monitor/tests/test_n8n_client.py
  • examples/agent-action-monitor/docs/gate-latency-notes.md
  • examples/agent-action-monitor/agent-demo/test_harness.py
  • examples/agent-action-monitor/src/dusk/config.py
  • examples/agent-action-monitor/src/dusk/trace/vector.py

…eline.py

target_tokens() used findall()[:_MAX_TOKENS], which still builds the full
match list before the slice runs, so a repetitive target could force an
unbounded match list even though only the first _MAX_TOKENS ever reached
the returned set. Switched to finditer() + islice() so the regex work
itself is bounded.

_flatten_scalars() checked its budget before decrementing for depth, so a
depth-rejected node never consumed budget, and the enclosing dict/list
loops kept enumerating every remaining item as a wasted no-op call once the
budget hit zero instead of stopping. Now decrements before the depth check
and breaks out of the loops as soon as the budget is exhausted.
@ritiksah141

Copy link
Copy Markdown
Author

Updating this comment now that CodeRabbit has also weighed in and its findings are addressed too. Three commits pushed since the review: rebased onto main, fixed everything below, and cleared CodeRabbit's own two follow-up passes (the second push introduced two small regressions of its own, which its next pass caught and are now fixed too).

Main blocker (benchmark). Rebuilt it so it actually demonstrates SIE is load bearing instead of being unable to show a difference by construction. Added held out negatives (legitimate actions the baseline never trained on, not the training set replayed as its own test) and a new attack built to evade every deterministic check: it reuses a known action type and target class for its agent, and its new tokens/values are kept deliberately outside the static sensitive frozenset. A new test scores that one attack twice, once with real SIE calls and once with them forced off, and asserts the score lands below gate_block_threshold without SIE and at or above it with SIE. Updated the README and docs/sie-primitives.md claims to match this rather than the previous "precision/recall unchanged" framing, which was true but didn't establish that SIE was actually carrying signal.

Out-of-the-box fixes:

  • SIE_API_KEY and the AWS credential vars now reach their containers through ${VAR} interpolation in compose.yml, defaulting to empty so the stack stays keyless by default. DUSK_ENFORCE is now overridable the same way.
  • The real Bedrock model id now uses the region-prefixed inference profile id (us.anthropic...) that Bedrock requires for on-demand invocation; AWS credentials are wired through compose.yml for when USE_REAL_BEDROCK=true.
  • Documented the actual cold start cost in the README: about 5.7 GB of weight download against the sie image's own roughly 150s healthcheck window, about 7 GB RSS once warm, and the Apple Silicon emulation penalty since the image is amd64 only.
  • Fixed the without-Docker flow: the gate's default port now matches the 8000 used everywhere else (was 5000), and the README now says to install agent-demo/requirements.txt and mock-prod/requirements.txt separately from the base package.
  • Swapped N8N_USER_MANAGEMENT_DISABLED (no-op since n8n 1.0) for N8N_DIAGNOSTICS_ENABLED=false.

Engine fixes:

  • A refusal caused only by the repeat-offense boost no longer gets recorded as a new offense with a fresh timestamp, which was defeating the decay on the offense it matched.
  • OffenseRecord now validates its timestamp is timezone-aware at construction, instead of letting a naive one reach _decay and raise later.
  • _load_gate_engine catches OSError broadly rather than only FileNotFoundError.
  • Added an in-process circuit breaker on the SIE call path so a half-up SIE cannot stall one gate request across 5 sequential near-timeout calls.
  • agent_id and target now reject control characters, closing the audit log line forging path.
  • Offense memory bounds bytes as well as counts, with a per-record token cap and a total byte budget that evicts the oldest agents once exceeded.

CodeRabbit's findings, in short (two passes, both now clean):

  • run_scenario_or_raise no longer raises for WOULD-BLOCK (the action was already applied in watch mode, so raising was hiding that from the caller)
  • DuskBlockedError handles an explicit reasons: None, not only a missing key
  • gate_block_threshold is now bounded to <= 1.0, since the score is always capped there and a threshold above it would silently make the gate never refuse anything
  • the n8n webhook executor's lazy singleton now uses the same double-checked locking as the gate engine's
  • feature extraction bounds traversal width, not just recursion depth, and actually bounds the scanning work rather than only the retained output (finditer/islice instead of findall()[:n], and the node budget now stops the enclosing loops rather than just no-opping through them)
  • ingest_file skips any malformed record, not only ones raising AdapterError
  • the SIE client is now cached per (endpoint, api key, timeout) instead of rebuilt on every call, and closed when the cache is invalidated
  • a stale doc claim that sie_encode is not on the /v1/gate request path is corrected
  • a duplicate assertion and two sleep-based flaky test patterns are cleaned up

Scope. Left the example self-contained rather than splitting the engine into its own repo. The engine at DUSK's own repo root has already diverged from what lives in this example, and dusk-security is not published anywhere installable yet, so a clean split would mean standing up real package publishing first. Open to revisiting once that exists.

Full test suite passes (189 passed, 3 skipped, the skips being the live SIE benchmark with no cluster reachable in that run), verified both with and without sie-sdk installed. Ruff, mypy strict, and bandit all clean.

@TFT444

TFT444 commented Jul 27, 2026

Copy link
Copy Markdown

Hi @fm1320

Following up. All of the feedback above has been addressed:

Rebuilt the SIE benchmark with held out negatives and an attack that evades the deterministic rules, caught only via extract or rerank, so the score now visibly lands on opposite sides of the block threshold with SIE on versus off. Fixed all six engine issues (repeat offense decay, timestamp validation, OSError handling, SIE stall circuit breaker, agent_id control characters, byte bounded caps).

Fixed the five out of the box items (.env wiring, Bedrock model ID, cold start docs, without Docker flow, n8n diagnostics flag). Rebased and resolved the conflict with main. Also worked through CodeRabbit's automated findings across two follow up passes.

Still owe you a proper answer on the scope question, thin integration layer versus engine in our repo. Will follow up here separately rather than bundle it with this fix pass.

Ready for another look whenever you have a chance.

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.

3 participants