Files
LangBot/plugins/SHARED_SOURCE_LEDGER.md
T

27 KiB

4.11 official Runner, KnowledgeEngine and misc source ledger

Integration base: origin/main at 9be9e9291ebc848c6fdd8006c50e6226bd822649 (fetched before integration). Each source tree is the Git tree object for the indicated plugin directory at commit bb34232fd (the shared-placement commit), unless the row says otherwise; resolve it at review time; it identifies committed source, not a built ZIP, normalized artifact digest, signature, or public marketplace version. Versions below are the integration manifest versions, not verified live versions. Author namespace is langbot-team; the table covers all 40 published IDs (13 Runner, 5 KnowledgeEngine, 22 misc).

Official ID Manifest version Source tree Placement claim Source lane
LocalAgent 0.2.1 3c30db89ad68360b1e21f47f4ceee672fe6836be shared candidate four Runner 8bb55ec
RunnerDemo 0.2.1 03bbc3189f7b482bd1284452ea09983a8cacb3bb shared candidate four Runner 8bb55ec
ACPAgentRunner 0.1.16 a5fcac59610a67957cc7c2b136eb55bb3dfb6d43 dedicated only nine Runner ede6756
ClaudeCodeAgent 0.1.14 d7e7442551d333166fc6fd6c2b863b3a1d4d89bb dedicated only nine Runner ede6756
CodexAgent 0.1.20 897281bc02835e73b1877acfb34aed51fd561763 dedicated only nine Runner ede6756
CozeAgent 0.2.2 68a70ed4c243ac525f3112f1cd455c08abad1719 shared candidate identity fix 88adecd
DashScopeAgent 0.2.2 5ffe08425aa5a797bf4997ef05f7a445391bfcbb shared candidate round-2 fix 55a5c76
DeerFlowAgent 0.2.1 4cfb268a2d37f42018fa18e4f47fc4e4aa2f8221 shared candidate shared adaptation 98c0957
DifyAgent 0.2.2 086e802f1de6cbe55c930917cb30de119b35a399 shared candidate identity fix 88adecd
LangflowAgent 0.2.2 9d042aa3c0790fca109f33cae6a001c5b190267f shared candidate round-2 fix 55a5c76
N8nAgent 0.2.2 eb6a1a3af25c88e72afb3524c99936091c061799 shared candidate identity fix 88adecd
TboxAgent 0.2.2 5bc35b3af58b63610575bb6a3757e74c0fa945a4 shared candidate identity fix 88adecd
WeKnoraAgent 0.2.3 96f95a55398e4bf7d518967c8635179df0280706 shared candidate identity fix 88adecd
DifyDatasetsConnector 0.2.1 7f6c4dc7833407be49eb2acf9957ee734fd90f7e shared candidate rejection fix e48fb85
FastGPTConnector 0.2.5 64bbad87f592d91c7a3b9fd4c39306762aac91ac shared candidate no cross-KB adoption 504cc46
LangRAG 0.2.2 df66cee971f7a6efdfe99700f8848358b1626e06 shared candidate rejection fix e48fb85
LongTermMemory 0.2.1 27ae4cc73c7da423c0eb86bf14b768ba16349e61 shared candidate longterm-memory 0.2.1 00b5869
RAGFlowConnector 0.2.4 ea170965afb27777d8449a92d45554c9210c5630 shared candidate no cross-KB adoption 504cc46
AgenticRAG 0.1.6 133c5f6e1b46945e89c478061bcea7f5606e43d5 shared candidate misc adaptation 98c0957
AIImagePlugin 0.1.6 c64cab897d550741bb90b00d523d9727c3128a30 shared candidate misc adaptation 98c0957
AutoTranslate 0.1.5 4c5feb7c5b83ed23613f71950148df69af9c3295 shared candidate misc adaptation 98c0957
DailyLimitPlugin 0.3.3 26d3dc3c7c2b1e4e9cd70815cfafa256c83b3fca shared candidate round-2 fix 55a5c76
EssentialCommands 0.1.10 ca30d9a12e6f7712337efae01a4db7ebe6a38082 shared candidate misc adaptation 98c0957
FAQManager 0.1.3 99a4f66cfc9964626120e257b9b17ff91f712590 shared candidate round-2 fix 55a5c76
GeneralParsers 0.1.11 65dc1b42004b965a7997f97466860238f51fea28 shared candidate round-2 fix 55a5c76
GitHubKit 0.2.2 cdda2dbe6a90943955e8facef598ab4baa4365c2 shared candidate misc adaptation 98c0957
GoogleSearch 0.1.7 28bc6e579f3a60f421efcce6f147d1bca24b9f36 shared candidate misc adaptation 98c0957
GroupChatSummary 0.1.4 5055015e9afb2105a25a4052260d5a1e078f3900 shared candidate misc adaptation 98c0957
HelloPlugin 0.1.4 15436f403fcba42f4a1c0b6d8d2fcc2f0c18c3dc shared candidate misc adaptation 98c0957
HumanTakeover 0.1.6 647be5ffec836431c0803eff2d9cffb8a89f16a3 shared candidate round-4 fix 447f267
KeywordAlert 0.1.3 64d94859264916fee18328b8bc9a1500da68a775 shared candidate misc adaptation 98c0957
MCBotPlugin 0.1.2 b3e59e3836c2a2661e4a334951a4a8e7fb7085ff shared candidate misc adaptation 98c0957
PowerContext 0.1.2 b288e471720bfabc50901578e509a3263c4bd65a shared candidate round-2 fix 55a5c76
QWeather 0.1.6 27586a59a7d0ac8ef9960e14d655cf566a1403ba shared candidate round-2 fix 55a5c76
ScheNotify 0.2.6 1178657c2e031a9a3fa4251c2cedbfb2f5daf6f2 shared candidate round-2 fix 55a5c76
SysStatPlugin 0.2.5 717e9a078a8bab7880496d022bce5acde886c582 shared candidate misc adaptation 98c0957
TavilySearch 0.1.5 e6ba92c17d8af6addbf5164f9a792e99259e9e9a shared candidate misc adaptation 98c0957
URLSummary 0.1.9 488921b8e8def507e5a6c84e69f7b22843bffa88 shared candidate round-4 fix 447f267
WebSearch 0.1.10 a5bf9fc52b3d5ac8d7ba90c311ac96c0fa1189ca shared candidate round-5 fix 649170e
WordFSRS 0.2.3 36b6d81abce98dcd315f134b8374a10682b6fb1a shared candidate misc adaptation 98c0957

Rows re-integrated after bb34232fd: LangRAG and LocalAgent at 9c68078, RunnerDemo at 31c3119 (its evidence test plus the component-order fix) and WeKnoraAgent at b085fdf, all 0.2.1. LangRAG fences a cancelled caller's dispatched vector mutation and a reply whose count is not a nonnegative integer again, and telemetry failures no longer fail the operation they describe; LocalAgent's tree differs only in removed tests and its lockfile version. RunnerDemo's tree adds its in-tree two-binding test and README notes; WeKnoraAgent's narrows the SHARED_RUNTIME.md identity paragraph to what the client actually sends. misc/GeneralParsers sits outside this ledger: its trimmed test tree moved it to 0.1.9.

Re-integrated at e48fb85 after the certification rejection: DifyDatasetsConnector, FastGPTConnector and RAGFlowConnector at 0.2.1 and LangRAG at 0.2.2 refuse a mutation whose persisted fence store cannot be read (the failed read is not cached), share one byte-identical dispatched() helper that fences before dispatch and clears the fence on a deterministic answer, and bind every recorded document mapping to the connector's normalized upstream target, so deletion refuses a mapping from another upstream before any remote call. CozeAgent, DifyAgent, N8nAgent and TboxAgent at 0.2.1 and WeKnoraAgent at 0.2.2 resolve upstream identities from the current invocation's installation binding and refuse in a shared worker that has none (five byte-identical scoped_identity.py files); Dify additionally accepts a legacy-owned human-input continuation once and rewrites it with the current owner. Evidence: certification suite 146 passed; LangRAG 19 through pytest and through the CI unittest runner; the shared agents' identity suites executed in CI by the isolated-interpreter harness; all five KnowledgeEngine archives built locally (DifyDatasetsConnector-0.2.1.lbpkg). The Windows review host cannot complete the SDK's POSIX-only RPC/file-transfer stage, so that stage remains CI-only.

Follow-up at 88adecd: the five shared agents (Coze/Dify/N8n/Tbox 0.2.2, WeKnora 0.2.3) no longer fall back to a per-run vendor identity — a sender-mode run without a trusted Host actor is refused with the plugin's own <plugin>.identity_unavailable code before any upstream client is built, and no computed identity contains a run id or a worker revision (WeKnora previously resolved its identity after constructing its client and now validates first, like the other four). http-certification-tests/test_isolation.py proves the refusal class and code, that run() reports a single run.failed carrying it, and that the vendor client class is never constructed; the 36 READMEs of the four user-id-source plugins and the five SHARED_RUNTIME.md files document the rule. Local evidence: 7/7 isolated plugin suites, 21/21 isolation certification cases, 570 passed with only the 6 known Windows-only vendor-worker failures, ruff clean. deerflow-agent and langflow-agent still carry an uncalled _get_user_tag with the old per-run fallback; it never reaches a vendor and was left untouched.

Follow-up at 057ace8 (second rejection of FastGPTConnector and RAGFlowConnector): both at 0.2.2 now make the pre-dispatch fence a precondition and release it only after the result is recorded — a failed Host write of the fence raises FencePersistError and aborts before any remote call (previously it was logged and the request was sent anyway, leaving a worker that had fenced the knowledge base in memory with no durable record), dispatched() refuses a knowledge base that is already fenced (FencedKnowledgeBaseError), and the wrapped operation covers the provider request, the response validation and the durable mapping/tombstone write, so an undecodable body or a cancellation landing after the acknowledgement keeps the knowledge base fenced. RAGFlow's GraphRAG/RAPTOR except Exception swallow is gone: a provider-answered non-zero code is a resolved answer while an unresolved outcome propagates and stops the chain, so RAPTOR's success can no longer clear GraphRAG's fence. Certification is 156 passed with 10 new cases, each shown to fail against the pre-change code. DifyDatasetsConnector keeps the previous best-effort persist and release-on-return behaviour: its 0.2.1 passed review and its artifact was deliberately left unchanged, so the stricter lifecycle is not asserted for it in the shared fixture.

Follow-up at 23e0717 (third rejection of the same two connectors): FastGPTConnector and RAGFlowConnector at 0.2.3 close three more findings. (1) is_ambiguous_http_failure released the protection for every HTTP status; it now releases only for a protocol-confirmed rejection — a 4xx response, or a connection that was never established — and keeps it for 5xx/gateway answers, so a 500 arriving after a possible commit no longer lets later mutations run over an unknown remote state (the FastGPT upload and delete handlers raise AmbiguousMutationError for those and report a resolved rejection only for a 4xx). (2) The first fence load was cached per installation without a lock while operations lock per knowledge base, so a snapshot whose read began before a sibling's clear could be installed afterwards and, because the binding was then marked loaded, block an already-resolved knowledge base indefinitely; the read is now single-flight under a per-binding lock and is discarded and re-read (bounded) when the fence generation changed under it. (3) RAGFlow mixed the knowledge-base identity with the collection identity: ingestion keyed config, mappings, locks and fences on context.get_collection_id() while creation and deletion used knowledge_base_id, so one knowledge base could hold two identities, lose mutual exclusion, and leave deletion unable to find the uploaded record; everything now keys on context.knowledge_base_id, _kb_identity prefers that attribute over the collection getter, and records already written under the collection key are migrated after a unique legacy match (an ambiguous match refuses and asks for reconciliation). Certification is 177 passed (21 new cases), each shown to fail against the pre-change code. DifyDatasetsConnector and LangRAG remain untouched: the shared classifier's 5xx rule is therefore not applied to DifyDatasets, which passed its own review.

Follow-up at 52465b4 (FastGPTConnector 0.2.4): the third rejection named the mixed knowledge-base/collection identity for RAGFlow only, but the host contract shows both connectors had it. LangBot/src/langbot/pkg/rag/knowledge/kbmgr.py puts 'knowledge_base_id': kb.uuid and 'collection_id': kb.collection_id or kb.uuid into the ingestion and retrieval contexts, while on_knowledge_base_create and call_rag_delete_document always pass kb.uuid; the SDK's get_collection_id() returns collection_id or knowledge_base_id, so a knowledge base bound to an existing vector collection gave ingestion a different key from creation and deletion — two locks, two fences and a document mapping that deletion could not find. FastGPT now keys the stored config, the upload-intent check, the pending intent, the created mapping, the deletion tombstone and the dispatch identity on context.knowledge_base_id, and _kb_identity prefers an argument's knowledge_base_id over its collection getter; records an earlier revision wrote under the collection key are migrated after a unique legacy match (an ambiguous match refuses). Its retrieval path needs no change: it is not serialized and reads only context creation settings. Certification is 180 passed (three new cases, each failing before the change), and the two bundles' shared functions are now identical modulo docstrings — is_ambiguous_http_failure, _load_fences, _read_fences, _bump_fence_generation, dispatched, fence, clear_fence, _kb_identity, _legacy_document, _migrate_legacy, _load_document, _save_document.

Follow-up at 504cc46 (fourth rejection of the same two connectors): the scan-based legacy migration the two previous rounds added was itself unsafe and is now removed from both bundles (FastGPTConnector 0.2.5, RAGFlowConnector 0.2.4). With a mapping recorded for knowledge base A and none for B, a delete under B adopted A's current record, copied A's configuration under B's key and — when the upstream target and dataset matched — deleted A's upstream document. ConfigStore._legacy_document, ConfigStore._migrate_legacy and the _load_document branch that called them are deleted; _load_document resolves only the exact mapping of the requested knowledge base (its canonical record, or a record that same knowledge base itself wrote and that carries the older Host revision's file UUID, still refusing an ambiguous match) and refuses when there is none, with no provider request and no storage write, so an ingest also no longer mistakes another knowledge base's pending record for its own intent. Historic collection-keyed data now moves to the canonical key only through explicit reconciliation. Certification replaces the two migration cases per plugin with a cross-knowledge-base deletion case (which fails against the pre-change code: delete_document('B', 'shared-doc') returned True and dispatched A's delete) and a no-false-intent case; the suite stays at 180 passed. The two notes above describe the migration this commit withdraws — they record history, not current behaviour.

Thirty-seven source candidates declare both shared-runtime-v1 and stateless-v1 (the 22 misc plugins joined at 98c0957); three stay dedicated (the three native coding Runners, whose process-local daemon hubs, shared workspace root and worker-HOME credentials cannot serve two installations from one process). The four KnowledgeEngine plugins moved from dedicated to shared candidate: their locks and fences are keyed by (installation binding, knowledge-base identity), persisted in installation-bound Host storage, released on installation revocation, and only a dispatched mutation with an unknown outcome fences. Ambiguous upstream mutations still need manual reconciliation, but that requirement is identical under dedicated and shared placement and is not a placement blocker. None is asserted certified or production-shared. The source tree identities include integration-only SDK requirement corrections: the four Runner candidates from 8bb55ec now require langbot-plugin>=0.7.4,<0.8, and LangRAG now requires the same instead of ==0.6.1; their source trees therefore intentionally differ from the lane tips. CI and contract corrections later changed some source trees; the per-plugin tree column identifies the current PR head, not the earlier lane tips.

misc adaptation at 98c0957: all 22 misc/* plugins and the three uncertified Runner agents now declare shared-runtime-v1 + stateless-v1. Each resolves tenant configuration and credentials per invocation, keys process-cached tenant state by the full installation binding (instance_uuid:workspace_uuid:installation_uuid, runtime revision excluded so an upgrade does not orphan rows) and releases it in on_installation_revoked(), carries that same scope token in its durable row keys (host row keys are [instance_uuid, workspace_uuid, owner_type, owner, key] and have no installation dimension), re-enters bind_invocation(handler, config=…, binding=…) for detached tenant work and registers it per binding, and moved blocking IO off the shared event loop (async clients or to_thread); one shared mutable default argument was fixed. Evidence: one suite per plugin, all passing, each fix recording the assertion that fails against the pre-change source (several reproduced against a HEAD copy, e.g. PowerContext's installation B posting installation A's token, DailyLimit's two installations writing one row, WordFSRS's two installations sharing one deck row, WebSearch's leaked default list), plus .github/workflows/misc-tests.yml running every misc suite in its own venv and interpreter. Runner/{dashscope,deerflow,langflow}-agent gained per-plugin suites and lost an uncalled per-run identity helper; Runner/acp-agent-runner, claude-code-agent and codex-agent remain dedicated by design. Known limits are stated per plugin (for example WordFSRS leaves pre-existing legacy deck rows unmigrated, so a placement switch starts empty; WebSearch's site adapters stay synchronous but run inside a worker thread). Declaring the markers is not a certification claim, and no archive was built from this head.

Round-2 fixes at 55a5c76 (11 of the 13 plugins resubmitted in the deployed batch were rejected): DashScopeAgent and LangflowAgent 0.2.2 build the optional asset gateway's shared listener and schedule its connection tasks in an explicit empty Context (create_task(..., context=contextvars.Context())), so the accept handle no longer retains the first invocation's configuration and revoked authority, and only a validated run token enters a registration's own context; coze/dify/n8n ship the same byte-identical pkg/asset_gateway.py and are already certified, so they were deliberately left untouched. WebSearch and URLSummary 0.1.6 gained their own safe_fetch module: scheme/host/port validation, refusal of non-public addresses, per-redirect-hop re-validation, connection to the validated IP literal, TLS verification restored (the removed ssl=False), raw and inflated byte caps, capped parse input, timeouts, cooperative abort and bounded concurrency in WebSearch. DailyLimitPlugin 0.3.3 and FAQManager 0.1.3 rebuild their admin pages with DOM APIs (no tenant value in an attribute or innerHTML sink) and DailyLimitPlugin runs read/modify/serialize/await-persist in one installation-level transaction with explicit rollback. HumanTakeover 0.1.4 re-validates the storage-failure barrier after acquiring the lock (only an explicit reconcile clears it), keys sessions by the trusted bot identity and freezes identity-less legacy rows; ScheNotify 0.2.6 re-enters bind_invocation(handler, config=…, binding=…) for detached sends (the pattern GitHubKit and GroupChatSummary passed with), restarts its loop on a worker upgrade, keys reminders by (bot_uuid, target_type, target_id) with ownership-checked deletion and keeps failed sends queued. PowerContext 0.1.2 drops the process-environment token fallback; QWeather 0.1.6 cancels and awaits every fan-out task before propagating; GeneralParsers 0.1.11 runs each PDF parse in a dedicated short-lived interpreter process (open → table detection → extraction → close), tears it down before a cancellation propagates, contains dependency crashes and pins PyMuPDF>=1.24.0,<2.0.0. Every finding ships a test whose assertion is recorded as failing against the pre-change sources; 22 misc suites pass locally and the Runner isolation harness runs 10 suites.

Round-3 fixes at 173d589 (3 of the 11 round-2 resubmissions were rejected again): HumanTakeover 0.1.5 attributes every console response to the session that requested it (monotonic load sequence plus the key the displayed data belongs to, in-flight responses invalidated and sending locked on a switch, an immutable captured reply target, and the same check on the takeover response and the poll writeback); URLSummary 0.1.7 and WebSearch 0.1.7 give fetch slots a single owner and an idempotent lease (the caller releases a failed submission, the worker releases in its own finally, the await is shielded so a queued future is never cancelled by an await-cancellation, and a running worker is never released early), redact userinfo/query/fragment from every error message, exception text and chained traceback, and enforce the absolute deadline and cancellation across WebSearch's whole synchronous lifecycle (DNS on a polled helper thread, deadline-bounded connect, and a watch thread that shuts the socket down so the deferred TLS handshake, status/header parsing and body reads all end). Certification status after this round: 22 of the 25 plugins resubmitted in the last two rounds are approved at their current versions (the 14 from the first batch — DeerFlowAgent, AIImagePlugin, AgenticRAG, AutoTranslate, EssentialCommands, GitHubKit, GoogleSearch, GroupChatSummary, HelloPlugin, KeywordAlert, MCBotPlugin, SysStatPlugin, TavilySearch, WordFSRS — and the 8 from the second — DashScopeAgent, LangflowAgent, DailyLimitPlugin, FAQManager, GeneralParsers, PowerContext, QWeather, ScheNotify), and the three fixed here are awaiting resubmission at 0.1.5 (HumanTakeover) and 0.1.8 (URLSummary and WebSearch are at 0.1.8: after the round-3 fixes, CI exposed that a socket shut down by the abort watch reads as a clean EOF on Linux, so an aborted fetch returned a normal truncated document; the EOF path now re-checks the stop flag and deadline, the post-read path raises FetchAbortedError, a short body against the declared Content-Length raises TruncatedResponseError, every exit path re-checks the abort state, and the tests are platform-independent through a scripted socketpair peer.

Round-4 fixes at 447f267: the three above were rejected once more and are now at HumanTakeover 0.1.6, URLSummary 0.1.9 and WebSearch 0.1.9. HumanTakeover keeps drafts per session (openSession saves the outgoing session's text and staged attachments, bumps a draft generation and restores the target session's) instead of only clearing pending values, attachment reads capture the session key and generation at selection time and are dropped unless both still match, attachments carry an owner and only target-owned ones are posted, and the reply result, draft cleanup and takeover writeback bind to the sending session and generation — closing the reviewer's probe where a late read staged one session's file in another's composer and a reply for one conversation repainted another. URLSummary and WebSearch replace the detached DNS helper thread with a fixed, backpressured resolver pool (four workers, a bounded semaphore over eight outstanding lookups taken non-blockingly at submission, ResolverBusyError once exhausted), and the worker releases the slot only after getaddrinfo really returns, so a timed-out or cancelled waiter never frees budget for a resolution still running; the reviewer's measurement (twelve timed-out fetches left twelve live resolver threads) now stabilises at four workers with eight in flight and controlled refusals. Both fetcher modules stay byte-identical apart from the docstring. The other 22 plugins of this batch remain approved at their current versions.

Round-5 fix at 649170e (WebSearch 0.1.10): HumanTakeover 0.1.6 and URLSummary 0.1.9 passed this round; WebSearch was rejected for one remaining hole — the encoded body was never capped and the decoder reused one zlib object while inspecting only unconsumed_tail, so data arriving after the first compressed stream ended sat in unused_data outside the decoded-size bound (the reviewer's probe returned the body OK with 8388608 trailing bytes retained and no size error). The encoded bytes read are now capped cumulatively (decoded cap plus one 64 KiB chunk of slack), exactly one compressed member is supported, and the decoder records the decompression EOF: it refuses to feed an end-of-stream decompressor, rejects trailing bytes that end inside a chunk, and refuses a source exhausted before the stream ends — which is what validates the gzip CRC or zlib checksum. misc/URLSummary is deliberately untouched: its certified 0.1.9 carries the same latent hole in its copy of safe_fetch.py, so the two modules now diverge by decision, documented in WebSearch's module docstring and SHARED_RUNTIME.md; fixing URLSummary would need its own round and a new version.

Evidence classes: The shared-Runner probe_two_bindings.py uses a real SDK Worker and synthetic Host/vendor fixture and covers all eight shared Runner agents (coze, dashscope, deerflow, dify, langflow, n8n, tbox, weknora), asserting one object graph, per-binding credentials and per-binding vendor calls; its synthetic artifact digest is not a built archive hash. The KE shared_subprocess_probe.py and checked-in JSON evidence use a real SDK child and local Host vector/storage/embedding fixtures; the probe now takes a connector directory too, so all five KnowledgeEngine candidates have real-child two-binding evidence, and verify_candidates.py builds and exercises all five archives, LongTermMemory included. LongTermMemory's former in-probe manifest override is no longer the placement difference. These demonstrate only the operations actually invoked, not real vendor/provider execution. RunnerDemo's two-binding proof is in-tree and reproducible now (RunnerDemo/tests/test_shared_runtime.py: one real SDK worker, two InstallationBindings, both resolving to the same PluginContainer, including through the handler's own slot registry; its identity assertions were shown to fail against a per-binding container mutation using a throwaway harness that is not committed). The earlier-lane archive f790d29d… is not in this tree and nothing here is the archive of this integrated PR head. The four-Runner lane does not provide real provider acceptance. Real provider-account invocation, signed/downloaded ZIP identity, independent review, production sandbox/network policy, and two real Cloud Workspace installations sharing the same certified digest remain unproven. External KE connector cancellation races and LongTermMemory's Tool/Command/EventListener write paths are still release gates; do not interpret a successful local fixture or a marketplace badge as production proof.

No archive was built or published from this integration head. Before any release, build each candidate from the exact committed source and record its immutable archive digest, dependency preparation, certificate fields/signature, downloaded public artifact readback, provider invocation, and two-binding Cloud object/PID/authority evidence separately. No public or production writes were performed by this integration.