Model or dataset
AhmadHassan-BTed/OpenOPC-Shadow-Adapter avatar
AhmadHassan-BTed/OpenOPC-Shadow-Adapter

OpenOPC-Shadow-Adapter parks agent tasks instead of timing them out

Zero-modification Human-in-the-Loop adapter for OpenOPC's agentic DAG runtime.

2,994 stars2 forksPythonMIT

At a glance

What is it?
OpenOPC-Shadow-Adapter is a plugin for the OpenOPC agentic DAG engine that intercepts a task needing human sign-off, writes it to an isolated SQLite WAL database, releases the engine thread immediately, and later resumes OpenOPC's own phase state machine. The value is in the non-blocking park, the cost is a second database and a portal to run alongside the engine.
Who is it for?
OpenOPC-Shadow-Adapter fits an OpenOPC operator whose workflows include a step that genuinely needs a person, and the parked task model is the right shape for approvals that can take hours while parallel branches keep running.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a DAG engine needs somewhere to wait

OpenOPC runs agentic work as a directed acyclic graph of parallel tasks, and the adapter exists to solve one specific failure that appears when a human is in the loop. A task that needs a sign-off takes longer than an engine timeout allows, and the standard outcome is a crash with the work lost. The comparison table in the README puts it plainly, listing engine crash and timeout failure for standard OpenOPC against zero timeouts and unlimited duration with the adapter, and state lost on restart against persistence in an isolated SQLite WAL database. The target user is an operator running OpenOPC workflows where a human reviews deliverables, sometimes much later, and where the rest of the graph should not stall while they are away. The project positions itself as a non-invasive extension that plugs into OpenOPC's external agent registry through ADAPTER_CLASSES["shadow"] with zero modifications to the core repository, which is the difference between a plugin and a fork.

Intercept, park, release inside 50 milliseconds

The mechanism is a three step intercept described as a non-blocking intercept under 50 milliseconds. The adapter catches the task when it is about to run, writes a record describing it into its own database, and returns AWAITING_HUMAN to the engine while releasing the execution thread immediately. That release is the whole trick. A blocking wait would occupy a worker slot and, in a graph with parallel branches, propagate the delay; returning control lets the independent branches finish and keeps the DAG alive. The parked record lives in a SQLite database opened in WAL mode, named shadow_tasks.db in the diagram, and it holds the task payload that a human will later act on. The adapter also assembles context for the reviewer, packaging ancestor deliverables, a structured markdown brief named brief_md, and any uploaded file artifacts under the CorporateArtifacts label, so the person reviewing does not start from nothing.

A React portal and a REST API for the people in the loop

Two entry points serve the human side. A React 19 interface with Tailwind is the reviewer portal, authenticated with JWT and bcrypt, and a FastAPI backend exposes the REST surface under the command shadow-serve. A second command, shadow-worker, is for the remote worker case, which the project frames as bring your own compute for roles that need a GPU or a person on another machine. The comparison table says the standard engine is a local user only, while the adapter provides a multi-user queue with JWT auth. The portal lists pending tasks from the parked database, shows the brief and any attachments for review, and posts the deliverable back through the REST API, which is what resumes the graph. Attachments are bounded on purpose: up to five files, a per file limit and a total payload limit, with an extension allowlist covering documents, archives and images. A rework path exists too, with a built in rework_requested state transition so a rejected deliverable goes back for another pass.

Resume reuses OpenOPC's own phase machine

What makes this an adapter rather than a wrapper is how the graph is unblocked. The adapter does not maintain its own copy of the workflow state. When a deliverable is submitted, it reaches into OpenOPC's native store.db, which the diagram shows running in WAL mode, and moves the delegation_work_items.phase field forward, and the downstream DAG nodes resume automatically. The env template names the file it expects at a default path of .opc/projects/default/store.db. In other words, the parking database holds only what the human side needs, while the authoritative workflow state stays where OpenOPC keeps it. That design has a practical consequence worth stating: two SQLite databases, two WAL files and two sets of write contention, and a resume that is only as reliable as the correctness of the phase transition the adapter performs on the engine's behalf.

Environment configuration for secrets, uploads and the port

Everything configurable lives in a .env template, and the security relevant keys are the first thing to set. The JWT settings include a secret placeholder, an algorithm and an expiry in hours, and the template instructs the operator to replace the secret with a strong random value. The upload guardrails are equally explicit:

bash
SHADOW_MAX_FILES_PER_SUBMISSION=5
SHADOW_MAX_FILE_SIZE_MB=10
SHADOW_MAX_TOTAL_UPLOAD_SIZE_MB=50

The extension allowlist is a single comma separated line that restricts what a contractor can attach, covering PDF, Word, Excel, PowerPoint, text, markdown, images and archives. The API server defaults to port 8800 on all interfaces with INFO level logging, and the database and upload directories sit next to the project root:

bash
SHADOW_API_PORT=8800
SHADOW_API_HOST=0.0.0.0
SHADOW_LOG_LEVEL=INFO

Binding to all interfaces while the token secret is still the shipped placeholder is the configuration mistake to avoid, since the portal is a multi-user surface by design. The dependency list explains the stack, with pydantic and pydantic-settings for configuration, aiosqlite for the async database access, FastAPI and uvicorn for the server, python-jose and passlib for tokens and password hashing, python-multipart for uploads, httpx for outbound calls and loguru for logging.

Install and run the adapter

The package is published on PyPI as openopc-shadow-adapter and is a normal pip install from a pyproject file that uses hatchling as its build backend. The project metadata is small and explicit:

toml
[project]
name = "openopc-shadow-adapter"
version = "1.0.0"

It requires Python 3.10 or newer, and the classifiers list 3.10, 3.11 and 3.12 as supported interpreters. The runtime dependencies are ordinary Python web packages:

toml
dependencies = [
    "pydantic>=2.0",
    "pydantic-settings>=2.0",
    "aiosqlite>=0.19.0",
    "fastapi>=0.104.0",
    "uvicorn[standard]>=0.24.0",
    "python-jose[cryptography]>=3.3.0",
    "passlib[bcrypt]>=1.7.4",
    "python-multipart>=0.0.6",
    "httpx>=0.27.0",
    "loguru>=0.7.0",
]

Two commands start the system. The server exposes the REST API and backs the portal, and is invoked as `shadow-serve`. The worker entry point for the remote worker role is invoked as `shadow-worker`. The repository also ships example_usage.py, a shadow_adapter/ package directory, a shadow_worker.py module and a tests/ directory, so the entry points are also importable as code if you would rather drive the adapter from a script than from the shell.

The release numbers do not agree

The version metadata is inconsistent across the project, and that is worth sorting out before you pin anything. The PyPI badge reads v0.1.0-ready, the README line above the badges says Production Release v0.1.0, the pyproject file declares version 1.0.0, and the repository's release list shows a v1.0.0 release dated 2026-08-20. The classifier in the package metadata also marks the development status as 4 - Beta, so the most conservative reading is that a 1.0.0 tagged release exists while the project still describes itself as beta. The last push was on 2026-08-20, the same day as that release, and the repository is not archived. The other limitation to keep in view is the premise itself: the adapter only helps if you are already running OpenOPC, and its whole value depends on the external agent registry contract staying stable, since it is registered under the shadow key and inherits the engine's assumptions about task shape.

Editorial conclusion

OpenOPC-Shadow-Adapter fits an OpenOPC operator whose workflows include a step that genuinely needs a person, and the parked task model is the right shape for approvals that can take hours while parallel branches keep running. It does not fit a team that wants one database to hold everything, because the adapter deliberately keeps its own shadow_tasks.db next to OpenOPC's store.db, and it does not fit a production deployment that copies the shipped environment template, since the example JWT secret is a placeholder that must be replaced. Before deploying it, set SHADOW_JWT_SECRET to a generated value, confirm SHADOW_OPC_STORE_PATH points at the store.db of the project you actually run, and check the version you install, because the PyPI badge, the README and the pyproject file disagree about whether the release is 0.1.0 or 1.0.0.

Frequently asked questions

What does OpenOPC-Shadow-Adapter actually do?

It intercepts an OpenOPC task that needs human sign-off, parks it in an isolated SQLite WAL database, returns AWAITING_HUMAN to the engine and releases the execution thread so other DAG branches keep running. A React portal and a REST API are used to review the task and submit the deliverable.

Does the adapter change the OpenOPC core?

No. It plugs into OpenOPC's external agent registry through ADAPTER_CLASSES["shadow"] as a ShadowModeAdapter, and the project describes itself as an independent extension with zero modifications to the core repository.

How is the human in the loop authenticated?

The portal uses JWT with bcrypt hashing, and the .env template carries SHADOW_JWT_SECRET, SHADOW_JWT_ALGORITHM and SHADOW_JWT_EXPIRE_HOURS, with an instruction to replace the placeholder secret with a strong random value.

What are the file upload limits?

The configuration allows five files per submission, 10 MB per file and 50 MB in total, and an extension allowlist restricts uploads to PDF, Office documents, text, markdown, images and archives.

Which version should I install?

The PyPI badge and the README say v0.1.0 while the pyproject file declares 1.0.0, and a v1.0.0 release is listed as dated 2026-08-20. The package metadata also marks the development status as Beta.

Official sources

  1. AhmadHassan-BTed/OpenOPC-Shadow-Adapter on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ahmadhassan-bted-openopc-shadow-adapter.svg)](https://hysenlabs.com/projects/ahmadhassan-bted-openopc-shadow-adapter)

Was this useful?