Skip to content

SDK 0.3: deep layout, tool guard, safeguards - #2

Merged
chiruu12 merged 16 commits into
mainfrom
dev
May 21, 2026
Merged

SDK 0.3: deep layout, tool guard, safeguards#2
chiruu12 merged 16 commits into
mainfrom
dev

Conversation

@chiruu12

Copy link
Copy Markdown
Collaborator

Summary

  • Reorganize SDK into api/, config/, safeguards/, orchestrators/, guards/, providers/
  • Add ToolGuard facade: filter tool/scrape output in one call (tool.filter(text))
  • Configurable blocked/review agent messages via TOML [messages]
  • Move injection patterns to safeguards/injection/patterns.py
  • Optional unplug[scrape] extra; server content provider stub
  • Deprecate unplug.scanners imports (shim remains)

Test plan

  • cd sdk && uv run pytest -q (327 passed)
  • uv run ruff check .
  • Greptile / Copilot review
  • Merge to main at 0.3.0 checkpoint only

Follow-up (separate branches)

  • feature/scrape — Firecrawl wrapper + ScrapeGuard
  • feature/safeguards-eval — per-category hardening vs neuralchemy eval

Made with Cursor

chiruu12 and others added 6 commits May 21, 2026 03:02
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings May 20, 2026 21:42

Copilot AI 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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

chiruu12 and others added 3 commits May 21, 2026 03:17
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@chiruu12

Copy link
Copy Markdown
Collaborator Author

@copilot review the PR

@greptile-apps

greptile-apps Bot commented May 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR is a major SDK reorganization into api/, config/, safeguards/, orchestrators/, guards/, and providers/ sub-packages, paired with the new ToolGuard and ScrapeGuard facades. It also addresses several issues from the previous review: the closing_delimiter false-positive on Markdown code fences is fixed (replaced with a narrower fence_role_injection pattern), the async event-loop re-entrancy crash is fixed via run_coroutine_sync, and __version__ is bumped to 0.3.0.

  • New ToolGuard / ScrapeGuard facades wrap the core Guard engine and expose synchronous and async entry points; run_coroutine_sync correctly threads around a running loop.
  • New safeguards/ layer moves injection patterns to safeguards/injection/patterns.py and re-exports the old unplug.scanners.* symbols as deprecation shims; backward compatibility is preserved.
  • config/ module consolidates all configuration models (GuardConfig, MessageConfig, LimitConfig) with TOML + env-var loading, and core/config_loader.py becomes a thin re-export shim.

Confidence Score: 5/5

Safe to merge; no blocking defects found in the reorganized SDK or new guard facades.

The core changes are all implemented correctly. Previous issues (closing_delimiter false positives, async event-loop re-entrancy, blocking Firecrawl call, version bump, ServerContentProvider resource leak) are all addressed. Remaining findings are style and robustness nits in the optional .env fallback path and the guards package namespace.

sdk/src/unplug/providers/content/env.py has the inline-comment stripping and hardcoded parent-depth issues worth a second look before the scrape feature lands.

Important Files Changed

Filename Overview
sdk/src/unplug/guards/init.py Exports filter at the top-level package namespace, shadowing Python's builtin filter for any from unplug.guards import * consumer.
sdk/src/unplug/providers/content/env.py Manual .env fallback parser strips inline comments, silently truncating API keys that contain #; also uses a hardcoded parents[5] index that can fail in non-standard install paths.
sdk/src/unplug/core/asyncio_compat.py New run_coroutine_sync correctly detects a running event loop and offloads to a dedicated thread to avoid asyncio.run() re-entrancy errors.
sdk/src/unplug/safeguards/injection/patterns.py The old closing_delimiter catch-all triple-backtick pattern is gone; replaced with narrower fence_role_injection and role_delimiter. The base64_payload FP pattern is also removed.
sdk/src/unplug/safeguards/registry.py New canonical SafeguardRegistry; _register_builtins imports InjectionScanner from the new canonical module, avoiding spurious deprecation warnings.
sdk/src/unplug/providers/content/server.py Now implements __aenter__/__aexit__, resolving the previous connection-leak concern.
sdk/src/unplug/orchestrators/base.py New orchestrator base types and scan_result_to_outcome helper look correct; action/safe mapping is well-handled.
sdk/src/unplug/guard.py Server mode wiring looks correct; limits check applied before delegating to UnplugClient; new config property and is_server_mode flag are clean additions.
sdk/src/unplug/providers/content/firecrawl.py Blocking scrape_sync is correctly offloaded with asyncio.to_thread in the async wrapper, fixing the previous review comment.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    User["Caller (sync or async)"] --> TG["ToolGuard.filter(text)"]
    User --> SG["ScrapeGuard.scrape(url)"]
    TG --> TOO["ToolOutputOrchestrator.run(text)"]
    SG --> SO["ScrapeOrchestrator.run(url) / run_coroutine_sync()"]
    SG --> SOA["ScrapeOrchestrator.run_async(url)"]
    SO --> RAC["run_coroutine_sync (asyncio.run or ThreadPoolExecutor)"]
    RAC --> SOA
    SOA --> FA["_fetch_async(url)"]
    FA --> FP["FirecrawlProvider or ServerContentProvider"]
    FP --> SC["ScrapedContent"]
    SC --> TOO
    TOO --> GS["Guard.scan(text)"]
    GS --> ServerMode{server_mode?}
    ServerMode -- yes --> UC["UnplugClient.scan()"]
    ServerMode -- no --> IP["InputPipeline (regex safeguards)"]
    GS --> SR["ScanResult"]
    SR --> SRO["scan_result_to_outcome()"]
    SRO --> CO["ContentOutcome (safe / blocked)"]
    CO --> TG
    CO --> SOA
Loading

Reviews (4): Last reviewed commit: "wire Guard server mode to HTTP client" | Re-trigger Greptile

Comment on lines +14 to +20
def _register_builtins() -> None:
from unplug.safeguards.injection import InjectionScanner
from unplug.scanners.destructive import DestructiveScanner
from unplug.scanners.financial import FinancialScanner
from unplug.scanners.harmful import HarmfulScanner
from unplug.scanners.leakage import LeakageScanner
from unplug.scanners.secrets import SecretsScanner

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 The new canonical SafeguardRegistry imports five scanners directly from unplug.scanners.*, which triggers unplug/scanners/__init__.py and emits a DeprecationWarning on every first SafeguardRegistry instantiation. Users of the new API will see a spurious deprecation warning pointing to code inside safeguards/registry.py, making it look like the SDK itself is using a deprecated module.

Suggested change
def _register_builtins() -> None:
from unplug.safeguards.injection import InjectionScanner
from unplug.scanners.destructive import DestructiveScanner
from unplug.scanners.financial import FinancialScanner
from unplug.scanners.harmful import HarmfulScanner
from unplug.scanners.leakage import LeakageScanner
from unplug.scanners.secrets import SecretsScanner
def _register_builtins() -> None:
from unplug.safeguards.injection import InjectionScanner
# TODO: move remaining scanners to unplug.safeguards.* and update imports below
from unplug.scanners.destructive import DestructiveScanner # noqa: PLC0415
from unplug.scanners.financial import FinancialScanner # noqa: PLC0415
from unplug.scanners.harmful import HarmfulScanner # noqa: PLC0415
from unplug.scanners.leakage import LeakageScanner # noqa: PLC0415
from unplug.scanners.secrets import SecretsScanner # noqa: PLC0415

Comment thread sdk/src/unplug/providers/content/firecrawl.py Outdated
Comment thread sdk/src/unplug/__init__.py Outdated
Comment thread sdk/src/unplug/safeguards/injection/patterns.py
Comment thread sdk/src/unplug/core/config_loader.py
Comment thread sdk/src/unplug/providers/content/server.py

Copilot AI commented May 21, 2026

Copy link
Copy Markdown

@copilot review the PR

Reviewed on commit b94ae75. I checked the previous failed Copilot review run and the failure was due to a GitHub Copilot rate-limit (HTTP 429), not a code issue. I also revalidated locally: make lint passed and make test passed (327 tests). No blockers found in this pass, so no code changes were needed.

Comment thread sdk/src/unplug/orchestrators/scrape.py
Comment thread sdk/src/unplug/guards/tool/__init__.py
@chiruu12
chiruu12 merged commit 078243c into main May 21, 2026
chiruu12 added a commit that referenced this pull request May 21, 2026
@chiruu12 chiruu12 mentioned this pull request Jun 11, 2026
3 tasks
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