feat: add database-backed distributed work runtime - #1446
Conversation
Implement durable work ledger, leader and worker leases, fencing, operation APIs, stateless MCP, role-based deployment, migrations, distributed Compose examples, and concurrency tests. Refs: oceanbase#1430
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces a broad set of coordinated changes across persistence/migrations, runtime coordination, HTTP/client behavior, and deployment modes that warrant careful human review of correctness and upgrade/rollback safety.
Pull request overview
Implements RFC #1430 distributed Server/Scheduler/Worker architecture by replacing the APScheduler sidecar with a database-backed Work Ledger + fenced leases, adding forward-only migrations + powercontext server migrate, and extending the HTTP/client/MCP surface for durable Operations, stateless multi-replica MCP, shared rate limiting, and expanded observability.
Changes:
- Introduces durable Work Ledger tables + coordination/membership/rate-limit tables, plus Alembic forward-only migrations and a Server
migrateCLI. - Adds distributed deployment roles (api/scheduler/worker), durable scheduler discovery and worker execution semantics, and corresponding tests/e2e coverage.
- Extends HTTP/client/MCP behavior for durable Operations (200/202 flush semantics, operation list/get/cancel/retry), shared fixed-window rate limiting, and updated tracing/metrics/docs + docker compose topology.
File summaries
| File | Description |
|---|---|
| zensical.toml | Adds RFC 1430 to nav |
| tests/test_server.py | Updates scheduler sidecar assertions |
| tests/test_server_tracing.py | Tracing tests for new links + DB |
| tests/test_server_roles.py | Verifies role route isolation + stateless MCP |
| tests/test_server_migration_cli.py | Tests server migrate CLI |
| tests/test_server_metrics.py | Adds bounded work/membership metrics checks |
| tests/test_mcp.py | Tests stateless MCP picker behavior |
| tests/test_docker_contract.py | Validates distributed compose contract |
| tests/test_client.py | Client tests for 202 operation polling/deadline |
| tests/e2e/test_shared_rate_limit.py | E2E rate limiting behavior |
| tests/e2e/test_scheduled_experience_incubation.py | Removes scheduler sidecar from e2e |
| tests/e2e/test_operation_api.py | E2E tests for stateless operation API |
| tests/e2e/test_observability.py | Updates spans/attributes for work stages |
| tests/e2e/test_durable_memory_work.py | E2E durable work dedupe + commit |
| tests/e2e/test_distributed_work_oceanbase.py | OceanBase multi-process/replica e2e |
| tests/e2e/real_experience_skill/harness.py | Harness updates for durable work |
| tests/builtin/runtime/test_operation_maintenance.py | Durable maintenance work retention test |
| tests/builtin/runtime/test_membership.py | Runtime membership + single-node lease tests |
| tests/builtin/runtime/test_durable_scheduler.py | Durable scheduler fencing/continuation tests |
| tests/builtin/runtime/test_deployment_config.py | Deployment/worker config validation tests |
| tests/builtin/persistence/test_rate_limit.py | Rate limit repository tests |
| tests/builtin/persistence/test_mysql_schema.py | Column budget updates (DateTime) |
| tests/builtin/persistence/test_migration.py | Migration lease + schema validation tests |
| tests/builtin/persistence/test_experience_index.py | Legacy schema upgrade path for projections |
| tests/builtin/persistence/test_coordination.py | Coordination lease/scan state tests |
| src/powercontext/server/tracing.py | Background span linking support |
| src/powercontext/server/settings.py | Adds deployment/coordination/worker/ops/rate_limit settings |
| src/powercontext/server/rate_limit.py | New shared DB rate-limit middleware |
| src/powercontext/server/principal.py | New principal context binding |
| src/powercontext/server/middleware.py | Principal binding + auth middleware updates |
| src/powercontext/server/metrics.py | Adds bounded durable-work + membership metrics |
| src/powercontext/server/mcp.py | Stateless MCP mode + internal bridge changes |
| src/powercontext/server/mapping.py | Adds Operation mapping utilities |
| src/powercontext/server/handoff_picker.py | Elicitation gating for stateless MCP |
| src/powercontext/server/factory.py | Role-aware server composition + work observer + rate limit |
| src/powercontext/server/cli.py | Adds server migrate command |
| src/powercontext/http/_generated/models.py | Generated Operation models |
| src/powercontext/http/init.py | Re-exports new HTTP models |
| src/powercontext/client/errors.py | Adds operation pending/failed errors |
| src/powercontext/client/init.py | Exposes new client errors |
| src/powercontext/builtin/runtime/work_observability.py | Work observer protocol + queue refresh helper |
| src/powercontext/builtin/runtime/scheduler.py | Removes APScheduler adapter |
| src/powercontext/builtin/runtime/relational.py | Adds scope keyset paging API |
| src/powercontext/builtin/runtime/protocols.py | Adds RuntimeTraceContext + optional accessor |
| src/powercontext/builtin/runtime/membership.py | Adds runtime membership + single-node lease |
| src/powercontext/builtin/runtime/durable_scheduler.py | Adds fenced durable scheduler |
| src/powercontext/builtin/runtime/config.py | Adds deployment/coordination/worker/ops/rate-limit config |
| src/powercontext/builtin/runtime/init.py | Removes legacy scheduled processor exports |
| src/powercontext/builtin/persistence/tables.py | Adds work/coordination/rate-limit tables + indexes |
| src/powercontext/builtin/persistence/sqlite/profile.py | Adds optional schema creation + tx serialization |
| src/powercontext/builtin/persistence/seekdb/profile.py | Adds optional schema creation toggle |
| src/powercontext/builtin/persistence/rate_limit.py | Adds DB-time fixed-window limiter |
| src/powercontext/builtin/persistence/oceanbase/profile.py | Adds optional schema creation toggle |
| src/powercontext/builtin/persistence/oceanbase/memory_index.py | Adds verify-only checks for provisioned indexes |
| src/powercontext/builtin/persistence/oceanbase/experience_index.py | Adds verify-only checks for projections/indexes |
| src/powercontext/builtin/persistence/migrations/versions/0002_work_ledger.py | Migration adds work/coordination tables |
| src/powercontext/builtin/persistence/migrations/versions/0001_baseline.py | Baseline migration |
| src/powercontext/builtin/persistence/migrations/script.py.mako | Forward-only migration template |
| src/powercontext/builtin/persistence/migrations/env.py | Packaged Alembic env (connection-provided) |
| src/powercontext/builtin/persistence/migrations/init.py | Migrations package marker |
| src/powercontext/builtin/persistence/migration.py | Migration orchestration + legacy stamping/validation |
| src/powercontext/builtin/persistence/memory_index.py | Adds schema verification hook |
| src/powercontext/builtin/persistence/database.py | Adds DB-time helper + insert-if-absent |
| src/powercontext/builtin/persistence/cursors.py | Uses insert-if-absent for CAS insert |
| src/powercontext/builtin/persistence/init.py | Exports new persistence primitives |
| scripts/generate_api.py | Updates generator for multiple 2xx responses |
| pyproject.toml | Adds Alembic, removes APScheduler |
| integrations/pi/plugins/powercontext/src/operations.generated.ts | Adds Operation endpoints to integration |
| integrations/opencode/plugins/powercontext/src/operations.generated.ts | Adds Operation endpoints to integration |
| integrations/dsh/plugins/powercontext/src/operations.generated.ts | Adds Operation endpoints to integration |
| docs/zh/rfcs/0020_runtime_backed_memory_remote_access.md | Notes RFC 1430 execution update |
| docs/zh/rfcs/0019_local_source_memory_runtime.md | Notes RFC 1430 execution update |
| docs/zh/rfcs/0011_remote_access_architecture.md | Notes RFC 1430 deployment boundary |
| docs/zh/docs/reference/interfaces.md | Updates scheduled Experience description |
| docs/zh/docs/reference/http-api.md | Documents durable flush + operations + 429 |
| docs/zh/docs/reference/configuration.md | Documents new distributed/lease/ops settings |
| docs/zh/docs/how-to/troubleshoot.md | Updates table list for export |
| docs/zh/docs/how-to/full-capability-runtime.md | Notes work state in primary DB |
| docs/zh/docs/how-to/deploy-server.md | Adds distributed role deployment guide |
| docs/zh/development/remote-access-implementation.md | Updates scheduling/ops surface docs |
| docs/zh/development/core-protocol.md | Updates scheduler/worker lifecycle wording |
| docs/en/rfcs/0020_runtime_backed_memory_remote_access.md | Notes RFC 1430 execution update |
| docs/en/rfcs/0019_local_source_memory_runtime.md | Notes RFC 1430 execution update |
| docs/en/rfcs/0011_remote_access_architecture.md | Notes RFC 1430 deployment boundary |
| docs/en/docs/reference/interfaces.md | Updates scheduled Experience description |
| docs/en/docs/reference/http-api.md | Documents durable flush + operations + 429 |
| docs/en/docs/reference/configuration.md | Documents new distributed/lease/ops settings |
| docs/en/docs/how-to/troubleshoot.md | Updates table list for export |
| docs/en/docs/how-to/full-capability-runtime.md | Notes work state in primary DB |
| docs/en/docs/how-to/deploy-server.md | Adds distributed role deployment guide |
| docs/en/development/remote-access-implementation.md | Updates scheduling/ops surface docs |
| docs/en/development/core-protocol.md | Updates scheduler/worker lifecycle wording |
| docker/README.md | Notes compose.distributed.yaml template |
| docker/compose.distributed.yaml | Adds migrator + role-separated topology |
| .env.example | Documents new deployment/work/ops/rate-limit env vars |
Review details
- Files reviewed: 112/113 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
There are a few concrete correctness/maintainability issues in the changed code (notably rate limiting behavior when unbound and duplicated exports) that should be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
scripts/generate_api.py:354
_success_response()now silently selects the lowest 2xx code when an operation defines multiple success responses. This can hide cases where different success codes use different schemas, leading generated clients to treat some successful responses as errors unless they are manually special-cased.
- Files reviewed: 115/117 changed files
- Comments generated: 2
- Review effort level: Lite
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Critical correctness issues remain in time handling, payload validation, and rate-limit middleware, alongside unresolved moderate findings.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (6)
Previously missed (5) — in code that hasn't changed since the last review.
docs/en/docs/how-to/troubleshoot.md:193
- Importing
pc_scheduler_leasesinto the freshly initialized target is not safe: migration always leaves aschema-migrationlease row in that table, so a current source export contains the same primary key andobloaderwill fail on the duplicate. The recovery procedure should provision the target withserver migrate(without starting the runtime) and exclude or explicitly reconcile transient lease/member rows before import.
docs/zh/docs/how-to/troubleshoot.md:186 - 把
pc_scheduler_leases导入刚初始化的目标库并不安全:迁移过程一定会在该表保留一条schema-migrationlease,当前版本源库也有相同主键,因此obloader会因重复键失败。恢复流程应仅运行server migrate来建目标 schema(不要启动 Runtime),并在导入前排除或明确协调临时 lease/member 数据。
src/powercontext/server/factory.py:111 - Distributed API replicas derive this response from their process-local inference adapters. The supplied Compose intentionally configures generation only on Workers, so APIs report
memory_extraction=falseeven though they accept durable flush work; clients that gate flushing on this capability will skip the feature. Derive execution capabilities from the distributed worker contract/topology rather than the API process's local pipeline.
docs/en/docs/how-to/full-capability-runtime.md:136 - This new paragraph contradicts the immediately preceding statement that SQLite still stores
scheduler.db, and repeats the same directory list. Remove the obsolete sidecar statement and keep one corrected description.
docs/zh/docs/how-to/full-capability-runtime.md:129 - 新增段落与前文仍声称会保存
scheduler.db相矛盾,而且重复列出了数据目录。请删除旧 sidecar 说明,只保留一份更新后的描述。
src/powercontext/builtin/persistence/init.py:97
- These two public export names are each listed twice. Remove the duplicates so
__all__remains a canonical, auditable API list.
- Files reviewed: 115/117 changed files
- Comments generated: 3
- Review effort level: Balanced
|
Addressed the remaining review feedback in b7343b4, including the summary-only findings: distributed API replicas now advertise worker-backed Memory extraction; the OceanBase restore guide provisions schema with server migrate and excludes transient coordination rows; and the obsolete scheduler.db wording was removed in both locales. The inline findings were also fixed and resolved: all generated 2xx response models, UTC database time, payload-version validation, shared public-route classification, and duplicate exports. Validation: make check; make test (1316 passed, 21 skipped); make contract-test (33 passed); make docs-test. |
…to feat/distributed # Conflicts: # src/powercontext/builtin/runtime/composition.py
Which issue or RFC does this PR close?
Closes #1430.
Implements the bilingual RFC added by this PR:
Rationale for this change
The existing background runtime depended on an APScheduler SQLite sidecar, process-local handler registration, and
process-local MCP session state. Those assumptions prevented API replicas from serving requests without affinity and
did not provide safe Scheduler failover or Worker recovery.
This PR makes OceanBase the single coordination and persistence authority for distributed execution. API replicas
enqueue durable work, Schedulers use a fenced database lease, and Workers use renewable fenced leases. Provider calls
remain at-least-once, while the final domain write, cursor advancement, attempt completion, and work success commit in
one transaction, so one logical Source window produces at most one database result.
The default deployment remains single_node/all. It now uses the same Work Ledger as distributed mode, avoiding two
different scheduling and recovery protocols.
What changes are included in this PR?
Architecture and persistence
deduplication, and strict per-lane sequencing.
fenced transaction.
Scheduler and Worker runtime
operator retry, and graceful draining.
External Skill targets.
HTTP, Client, MCP, and authorization
operation methods.
sticky sessions.
Operations and observability
Work rows, attempt rows, logs, metrics, and traces.
replicas.
documentation.
Code cleanup
observability exception handling, and unused runtime configuration.
payload versions, and stored-data decoding.
Are there any user-facing changes?
Yes.
database and one role per process.
DDL automatically.
PowerContextClient.flush_memory() callers continue to receive FlushMemoryResponse unless their total deadline
expires, in which case OperationPendingError includes the operation ID.
APScheduler sidecar is not deleted automatically.
The OpenAPI contract and generated clients were regenerated from openapi/powercontext.yaml.
How was this change tested?
Automated validation
The final branch was validated with:
make check make test make contract-test make docs-testResults:
The real OceanBase acceptance tests were also run against a dedicated OceanBase 4.3.5.6 MySQL-mode database:
POWERCONTEXT_TEST_OCEANBASE_URL='mysql+aoceanbase://<user>:<password>@<host>:2881/<database>' \ uv run pytest -q tests/e2e/test_distributed_work_oceanbase.pyResult: 2 passed. These tests verify a single claim across two real Worker processes under Read Committed isolation,
plus cross-replica HTTP operation visibility and stateless MCP round-robin behavior.
Additional tests cover:
Actual distributed Docker Compose test
The deployment example was started against a clean OceanBase 4.3.5.6 instance using the local Server image and a
deterministic OpenAI-compatible model stub. No external model service was called.
Topology exercised:
Observed results:
The Worker failure test used a 12-second lease, 3-second heartbeat, 9-second shutdown grace, concurrency 1, and a model
stub that delayed generation for 25 seconds. This made lease recovery observable without changing production defaults.
Basic reproduction flow
Build the local image:
Configure a dedicated OceanBase database and provider credentials, then start the topology:
The migrate service should exit with status 0. Both API readiness endpoints should report ready or degraded only for
an intentionally absent optional dependency:
Exercise cross-replica operation visibility:
Poll the final command until the operation becomes succeeded. For fault injection, stop the current Scheduler and
confirm its standby takes over with a higher database fence. Stop a Worker during the delayed provider call, wait for
lease expiry, and confirm another Worker completes the operation while the original attempt is recorded as
lease_expired_retry.
Finally, inspect bounded metrics and clean up:
The temporary Compose services, network, model stub, and OceanBase test data used in the actual run were removed after
validation.
AI usage statement