Skip to content

Consolidate the agent publish/share surface onto the canonical KA lifecycle (#1087)#1265

Open
Jurij89 wants to merge 47 commits into
mainfrom
feat/api-agent-tooling-cleanup
Open

Consolidate the agent publish/share surface onto the canonical KA lifecycle (#1087)#1265
Jurij89 wants to merge 47 commits into
mainfrom
feat/api-agent-tooling-cleanup

Conversation

@Jurij89

@Jurij89 Jurij89 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • One canonical way to publish. Agents now have exactly one path to put knowledge into SWM/VM — the named Knowledge-Asset lifecycle (create → write → seal → share → publish). This deletes the bypass/loose agent tools dkg_publish (direct raw-quad mint), dkg_shared_memory_publish, and dkg_share across all three adapters (MCP, OpenClaw, Hermes), and re-points every internal caller onto the canonical per-KA POST /api/knowledge-assets/:name/vm/publish.
  • Deletes the legacy bridge route + a latent authz bypass (Track C follow-up: numeric publishContextGraphId preflight bypass #1179). Removes POST /api/shared-memory/publish entirely — both the assertionName and selection forks — and with it the Track C follow-up: numeric publishContextGraphId preflight bypass #1179 numeric-publishContextGraphId branch that trusted a caller-supplied on-chain CG id without an ownership check. The surviving publish routes enforce the ownership check.
  • Adds an ergonomic, lifecycle-respecting one-shot (D3). Extends dkg_knowledge_asset_create with optional quads + alsoShareSwm so an agent can create → write → seal → share in one call. It is default-false (sharing must be explicit), stops at SWM (never mints to VM), and is byte-parallel across all three adapters — so it cannot reintroduce a one-call bypass.
  • Migrates the node-UI publish CTAs. The "Publish in SWM" and per-entity "Ratify" buttons now publish per-assertion via /vm/publish (named-only), with a catch 409 → seal-in-SWM → retry-once safety net; this also fixes a latent crash when a CG held ≥2 SWM roots.
  • Rewrites the agent docs. Both SKILL.md files teach the single canonical flow and scrub every agent-facing loose-SWM/bridge reference; .cursor/rules updated; the skill-endpoint contract test is flipped to assert the bridge route is absent.
  • Deliberately deferred (out of scope, tracked in Consolidate daemon-level loose SWM writes onto the named-KA lifecycle (post agent-tooling cleanup) #1260). The daemon-level loose-SWM write primitive (/api/shared-memory/write + /conditional-write) is kept — it is the staging buffer the source-worker bulk-ingest + async-LIFT pipeline depends on, and a live consumed channel (dkg_query SWM view, node-UI, gossip/sync). The SWM gossip/sync receive fabric (sync/host routes + handlers) is untouched. Three now-dead but publicly re-exported facade methods (DkgClient.publish, DkgPublisherExtension.publishVerifiableMemory/writeSharedMemory) that still call surviving routes are left for that follow-up.

This change was implemented in waves and gated by a multi-angle adversarial review (plan-conformance, over-deletion, #1179 security, correctness, doc-truth, cross-adapter parity) before opening.

Related

Files changed

File(s) What
packages/cli/src/daemon/routes/memory.ts Delete the entire POST /api/shared-memory/publish route (both forks) + the #1179 numeric-CG authz-bypass branch + the helpers it solely used; keep /write, /conditional-write, /memory/turn, and all sync/host routes
packages/cli/src/api-client.ts publishFromFinalizedAssertion delegates to /vm/publish; deleted the publishFromSharedMemory selection-fork shim
packages/cli/src/commands/shared-memory.ts, benchmark/publish-get/* Re-point the CLI publish command + the publish-get benchmark onto the named-KA /vm/publish flow
packages/mcp-dkg/src/tools/publish.ts (deleted), tools/assertions.ts, index.ts, client.ts Delete dkg_publish/dkg_shared_memory_publish; extend dkg_knowledge_asset_create (D3); scrub the surviving publish description
packages/adapter-openclaw/src/{tools/*,DkgNodePlugin.ts,dkg-client.ts,index.ts} Delete dkg_publish/dkg_shared_memory_publish/dkg_share; extend create (D3); delete the 2 bridge-calling facade methods
packages/adapter-hermes/hermes-plugin/{__init__.py,client.py}, src/types.ts, README.md Delete the 3 tool schemas + handlers + dead client methods; extend create (D3); narrow the publish-exposure gate 3→1; system-prompt surgery; keep dkg_memory
packages/core/src/publisher-extension.ts Delete the bridge-calling publishSharedMemory facade method + its transport member + dead request type
packages/node-ui/src/ui/{api.ts,views/MemoryLayerView.tsx,views/project/components/ka.tsx,hooks/useMemoryEntities.ts} Migrate the publish CTAs to per-KA /vm/publish (named-only) with a seal-retry chokepoint; remove dead loose-write code
packages/network-sim/src/{api.ts,components/ControlPanel.tsx,server/sim-engine.ts} Re-point sim publish to /vm/publish; delete the loose-publish-by-selection helper + its button
packages/cli/skills/dkg-node/SKILL.md, dkg-importer/SKILL.md, .cursor/rules/dkg-annotate.mdc Rewrite to the single canonical flow; scrub all agent-facing loose-SWM/bridge references
tests across cli/mcp/openclaw/hermes/node-ui/core Update/flip tests; flip the skill-endpoint contract; delete the obsolete selection-fork + multi-root tests

Test plan

  • Build dkg-core/dkg-storage/dkg-query, then run the impacted suites:
    • pnpm -C packages/mcp-dkg test (227 pass; 2 known unicode inject-hook flakes)
    • pnpm -C packages/adapter-openclaw test (1093 pass / 1 skip)
    • PYTHON=python pnpm -C packages/adapter-hermes test (vitest 90) + pytest (113)
    • pnpm -C packages/node-ui test (1273 pass; the better-sqlite3 native-binding suites are pre-existing env failures)
    • pnpm -C packages/cli test (PR-touched suites green: auth, api-client, skill-endpoint)
    • pnpm -C packages/core test (1084/1084)
  • Clarify knowledge asset lifecycle when SWM share succeeds without a seal but VM publish requires finalization #1116 regression gate green: knowledge-assets-1116-share-errors, share-warning-parity, agent e2e-memory-layers.
  • Verify zero agent-facing references to the deleted tools/routes in either SKILL.md; the D3 default-false / no-leak guard tests are green; cross-adapter parity holds.
  • Manual behavioral checks: dkg_knowledge_asset_create({quads, alsoShareSwm:true}) stops at SWM (no VM mint); publishing a named SWM assertion via /vm/publish works; the node-UI publish button works for a named SWM root and is absent for un-named loose roots.

Notes / known risks

  • OpenClaw doc refresh: existing OpenClaw nodes must re-run dkg openclaw setup to pick up the new SKILL.md — the daemon serves /.well-known/skill.md live (MCP/Hermes/HTTP get it immediately), but OpenClaw copies the file into the agent workspace at setup.
  • Deferred / follow-up (intentionally out of scope, for transparency):
    • The daemon-level loose-SWM write primitive (/api/shared-memory/write + /conditional-write) — kept; the source-worker bulk-ingest + async-LIFT pipeline depends on it. Consolidating it onto the named-KA model is tracked in Consolidate daemon-level loose SWM writes onto the named-KA lifecycle (post agent-tooling cleanup) #1260.
    • devnet / ops validation tooling (all out-of-CI). The cleanly-isolated callers are migrated here: 4 devnet test suites (v10-core-flows, agent-provenance, rich-scenario, v10-stress) re-pointed to the named-KA flow, the publish-loop.mjs + bootstrap.cjs scripts migrated, and the 3 ops .mjs (drain-swm-duplicates, redistribute-memory, seed-dkg-code-project) neutered (publish leg disabled + marked). The interconnected .sh validation suite is deferred to Consolidate daemon-level loose SWM writes onto the named-KA lifecycle (post agent-tooling cleanup) #1260 and left untouched — the 10 .sh validators all publish through one shared bash helper (devnet_publish_swm_all_roots, 100% loose selection-publish) that ~9 more rfc38/rfc39 scenarios source; re-pointing it requires a per-scenario named-KA re-validation against a live devnet (mechanically gutting it would ship non-functional validators). Full scope documented in #1260. (A compat handler was rejected — it would resurrect the bridge route + the Track C follow-up: numeric publishContextGraphId preflight bypass #1179 authz-bypass.)
    • entities.tsx pre-register — both sibling CTAs (entities.tsx, layer-widgets.tsx) now publish through the shared knowledgeAssetPublishWithSeal wrapper (seal-retry + 207 warning), and layer-widgets' redundant ensureContextGraphOnChain pre-register was removed. The one remaining deferred item is entities.tsx's pre-register, which is shared with the finalize/promote branch (removing it needs the promote-path analysis) — pre-existing, not introduced here.
    • 3 now-dead but publicly re-exported facade methods (DkgClient.publish, DkgPublisherExtension.publishVerifiableMemory/writeSharedMemory) that still call surviving routes — left for a future library-API cleanup.
  • Pre-existing env-gated suites (better-sqlite3 native bindings, live-daemon/real-chain, Windows path-separator, comma-locale render) are unrelated to this PR.

Comment thread packages/node-ui/src/ui/views/MemoryLayerView.tsx Outdated
Comment thread packages/node-ui/test/ui-api-pure.test.ts
Jurij89 pushed a commit that referenced this pull request Jun 21, 2026
… seal-retry recovery

Addresses the PR #1265 review (otReviewAgent).

- Removed the ensureContextGraphOnChain pre-call (+ unused import) from both publish
  CTAs (MemoryLayerView publishAssertions, ka.tsx VerifyOnDkgButton). The daemon
  /vm/publish route deliberately runs local preconditions FIRST and only registers the
  CG on the CG_NOT_REGISTERED retry path, rehydrating the STORED publishPolicy. The
  pre-call burned registration gas on a doomed publish and risked the #1085
  default-policy (the UI register call sends no policy). The CTAs now call
  knowledgeAssetPublishWithSeal directly and let the daemon register at the right time.
- Added 3 tests for the recovery contract: 409 PUBLISH_NOT_FULL_SHARE and
  VM_PUBLISH_PRECONDITION each -> wm/finalize{layer:'swm'} -> retry-once succeeds;
  SWM_SUBSET_NOT_SEALABLE -> typed throw, no retry loop.

tsc clean; ui-api-pure 52/52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread packages/cli/src/benchmark/publish-get/runner.ts Outdated
Comment thread packages/node-ui/src/ui/api.ts
Comment thread packages/node-ui/test/context-graph-empty-stat-components.test.ts
Comment thread packages/cli/test/context-graph-write-path-validation.test.ts
Jurij89 pushed a commit that referenced this pull request Jun 21, 2026
…deleted publish route

Addresses 2 PR #1265 review comments.

- publish-get benchmark: the sync-leg KA name was benchmark-sync-<runId>-<iteration>,
  but runIteration runs for both warmup and measured phases with the same numeric
  iteration, so warmup-N and measured-N collided (name/idempotency-based create) and
  the GET validated the wrong marker. Name now includes the phase
  (benchmark-sync-<runId>-<warmup|measured>-<iteration>), mirroring the payload's own
  warmup-/measured- root+marker uniqueness.
- context-graph-write-path-validation: added a negative test that POSTs the deleted
  /api/shared-memory/publish with a VALID CG and asserts 404 (unambiguous
  route-not-found) -- guards the route deletion against accidental reintroduction.

cli tsc clean; the benchmark unique-roots + marker tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Jurij89 pushed a commit that referenced this pull request Jun 21, 2026
…te the api mocks

Addresses 2 PR #1265 review comments.

- /api/knowledge-assets/:name/vm/publish returns 207 when the KA minted on-chain but
  the CG binding failed (contextGraphError in the body; post() treats 207 as ok, so it
  rides through un-thrown). Typed contextGraphError on PublishResult +
  knowledgeAssetPublishWithSeal, and surfaced it: ka.tsx Verify CTA renders a warning
  ("binding incomplete") instead of a clean check; MemoryLayerView counts a `partial`
  tally, prefers a clean sample, and downgrades the result card to a warning when
  partial>0. Added a ui-api-pure test (207+contextGraphError resolves, no seal/retry).
- Completed the full vi.mock('../src/ui/api.js') in the two component tests that load
  the components barrel (-> ka.tsx) with the new exports it imports
  (knowledgeAssetPublishWithSeal + the real SwmSubsetNotSealableError class), avoiding
  a missing-mock module-load failure.

tsc clean; ui-api-pure 53/53; component-mock suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread packages/node-ui/src/ui/views/project/components/ka.tsx Outdated
Comment thread packages/adapter-hermes/pytests/test_no_per_quad_graph.py Outdated
Comment thread packages/adapter-hermes/test/hermes-adapter.part-14.test.ts Outdated
Comment thread packages/cli/skills/dkg-node/SKILL.md
Comment thread packages/adapter-hermes/hermes-plugin/__init__.py Outdated
Comment thread packages/cli/src/daemon/routes/memory.ts
Comment thread packages/cli/test/context-graph-write-path-validation.test.ts

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

Operational Notice: Review Agent could not complete this review.

Synthesizer produced only invalid comment anchors.

Comment thread packages/cli/src/benchmark/publish-get/types.ts
Comment thread packages/node-ui/src/ui/api.ts
Comment thread devnet/rich-scenario/automated.test.ts Outdated
Comment thread scripts/redistribute-memory.mjs
Comment thread packages/mcp-dkg/src/tools/assertions.ts Outdated
@Jurij89

Jurij89 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

✅ Ready for human review/merge — convergence reached

All CI checks green (38 pass / 5 skip / 0 fail); every review thread resolved across 10 rounds with otReviewAgent, on top of 2 internal adversarial review passes before opening.

What shipped (#1087)

  • Deleted the 3 bypass/loose agent toolsdkg_publish, dkg_shared_memory_publish, dkg_share — across MCP, OpenClaw, and Hermes. One canonical publish (dkg_knowledge_asset_publish → per-KA /vm/publish) remains.
  • Deleted the POST /api/shared-memory/publish bridge route (both the assertionName and selection forks) and with it the Track C follow-up: numeric publishContextGraphId preflight bypass #1179 numeric-publishContextGraphId authz-bypass branch that lived in that handler.
  • D3 one-shot: extended dkg_knowledge_asset_create with {quads, alsoShareSwm} — default-false, stops at SWM, never mints to VM, byte-parallel across all 3 adapters incl. shared RDF-object auto-typing.
  • Migrated the node-UI publish CTAs to per-KA /vm/publish through one centralized knowledgeAssetPublishWithSeal wrapper (catch-409 → seal-in-SWM → retry, + 207 partial-publish warning).
  • Rewrote SKILL.md ×2 + docs to the single canonical create → write → seal → share → publish flow; scrubbed every agent-facing loose-SWM/bridge reference; added the camelCase/snake_case param-spelling note.

Deferred (tracked in #1260, untouched here)

  • The daemon-level loose-SWM write primitive (/api/shared-memory/write) — the source-worker bulk-ingest + async-LIFT pipeline depends on it.
  • The interconnected devnet/ops .sh validation suite (shared devnet_publish_swm_all_roots helper) — needs a coherent named-KA re-validation against a live devnet.

Product/agent surface has zero callers of the deleted route. Ready for human review + merge.

Comment thread bench/analyze-publish-async-get.ts Outdated
Comment thread packages/cli/src/api-client.ts
Comment thread packages/node-ui/src/ui/views/MemoryLayerView.tsx
Comment thread packages/mcp-dkg/src/tools/assertions.ts

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

Operational Notice: Review Agent could not complete this review.

Synthesizer produced only invalid comment anchors.

Comment thread packages/node-ui/test/memory-layer-publish-panel.test.ts Outdated
Comment thread packages/node-ui/src/ui/views/project/components/entities.tsx

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

Operational Notice: Review Agent could not complete this review.

Synthesizer produced only invalid comment anchors.

Comment thread packages/node-ui/test/memory-layer-publish-panel.test.ts
Comment thread bench/publish-async-get.bench.ts Outdated
Comment thread bench/publish-async-get.bench.ts Outdated

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

Operational Notice: Review Agent could not complete this review.

Synthesizer produced only invalid comment anchors.

@Jurij89

Jurij89 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

✅ Converged — all review rounds resolved, CI green

Following up on the summary above: review continued through several more rounds (all now resolved), including:

  • 🔴 Publish-time CG registration: removed a client-side ensureContextGraphOnChain pre-register from the node-UI publish/promote CTAs that defeated the /vm/publish register-then-mint contract (it burned registration gas on doomed publishes and needlessly fired for off-chain promote). Verified against the daemon contract (knowledge-assets.tsensureRegisteredForPublish, Clarify knowledge asset lifecycle when SWM share succeeds without a seal but VM publish requires finalization #1116).
  • 🔴 Bench correctness: KA names now derive from the safe run-id slug (not the RDF root URN that validateAssertionName rejects); dropped the redundant SWM pre-write from the sync ESBench flow.
  • Test coverage: api-client /vm/publish serialization, the node-UI publish panel (name+subGraphName forwarding, 207 partial-detail, same-name graphUri disambiguation), and an MCP RDF-object-normalization conformance guard (24 golden cases, byte-verified against core).

Final state: CI green (38 pass / 5 skip / 0 fail), 0 unresolved review threads, MERGEABLE. Ready for human review + merge.

Jurij Skornik and others added 28 commits June 24, 2026 20:38
…) param spelling

otReviewAgent: the shared dkg-node SKILL documents the D3 flag as camelCase
`alsoShareSwm`, but the OpenClaw + Hermes tools expose all fields in snake_case
(`also_share_swm`, `context_graph_id`, `sub_graph_name`). An agent copying the example
on those adapters would pass a key they ignore -- sealing the asset but silently not
sharing it. Added a parameter-spelling note to the Tool-vs-HTTP section (the systemic
fix; applies to every param, not just this flag).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…deleted bridge route

otReviewAgent (escalated): the deleted /api/shared-memory/publish left active devnet
callers 404-ing. Re-pointed the 3 PURE-assertionName files (no selection sibling, so
they become fully working) to /api/knowledge-assets/:name/vm/publish, mirroring the W1
ApiClient migration:
- devnet/v10-core-flows/automated.test.ts (:283, :448)
- scripts/testnet-publish-stress/publish-loop.mjs (:299)
- devnet/_bootstrap/bootstrap.cjs (:353, :375)
Response shape verified statically: /vm/publish returns kaId/status/txHash/blockNumber
via the same engine publishFromFinalizedAssertion as the deleted fork, so the test
assertions hold unchanged.

The remaining callers are MIXED (assertionName + selection in one file) or pure
selection (loose publish-by-selection) -- they need the named-KA rework deferred to
#1260; a compat handler is rejected (it would re-introduce the bridge route + the #1179
authz-bypass). Devnet-only, not in CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…publish route

Round-8 batch 1 of completing the caller cleanup (no product callers remain; these are
out-of-CI devnet validation suites). otReviewAgent: the route deletion must be matched
by a complete caller migration.

- agent-provenance + rich-scenario: the selection-publish (loose write + {rootEntities}/
  {selection:'all'}) steps became a named-KA flow (create one-shot {finalize,
  alsoShareSwm} -> /api/knowledge-assets/:name/vm/publish); the assertionName helpers
  -> /vm/publish. Provenance/status/kaId/txHash assertions preserved (same engine).
- v10-stress: assertionName publish -> /vm/publish; RETIRED the obsolete residual-SWM
  drain (it pre-drained loose SWM the removed bridge created -- the named-KA flow leaves
  no residue). Updated the FINDINGS repro string + doc comments.

All 3 now have zero /api/shared-memory/publish references. Devnet-only (separate
harness, not in CI); response-shape compatibility static-verified (W1 in-CI proof).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… pending #1260)

The selection-publish ops scripts used the removed loose publish-by-selection. Per
decision, neutered (not deleted) + marked for the #1260 named-KA rework:
- drain-swm-duplicates.mjs: DISABLED header (a named KA self-drains its roots on
  vm/publish, so this may be obsolete); the per-batch publish loop is now a logged no-op.
- redistribute-memory.mjs: the SWM->VM leg is a logged no-op (WM/SWM legs intact).
- seed-dkg-code-project.mjs: dropped the best-effort step-4 VM publish (seed still
  populates WM+SWM).
All three: zero /api/shared-memory/publish refs; node --check passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on interface

otReviewAgent: the W3.5 BenchmarkClient interface change (publishFromSharedMemory ->
publishAssertion in publish-get/types.ts) orphaned the checked-in bench/ implementation,
breaking the bench TS build / pnpm bench (out-of-CI, hence the matrix stayed green).
Updated bench/support/layered-dkg-client.ts (method + in-memory impl) + the 2 esbench
scenarios (analyze-publish-async-get.ts, publish-async-get.bench.ts) to the named-KA
publishAssertion flow, mirroring packages/cli/src/benchmark/publish-get/runner.ts.

tsc -p bench/tsconfig.json clean; zero publishFromSharedMemory refs in bench/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the seal-retry wrapper

otReviewAgent: these 2 CTAs called knowledgeAssetPublish directly, bypassing the
catch-409->seal->retry + 207 partial-publish warning that knowledgeAssetPublishWithSeal
centralizes. Switched both to the wrapper and surfaced contextGraphError via the shared
partialPublishWarning (matching MemoryLayerView + ka.tsx). Removed the now-redundant
ensureContextGraphOnChain pre-register in layer-widgets (the publish path was
separable); kept it in entities (shared with the finalize/promote branch — that
pre-register removal stays deferred, flagged with a NOTE).

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hes; coherent redistribute neuter

otReviewAgent round-10:
- The batch-1 devnet migration carried clearSharedMemoryAfter:true onto the per-KA
  /vm/publish, which the daemon honors as a CG-WIDE SWM wipe (clears ALL unpublished
  SWM in the CG, not just the published KA's roots) -- it would clobber the SWM-only
  assets staged earlier in the same CG. Removed the flag from the 3 migrated sites
  (rich-scenario x2, agent-provenance); the per-KA publish already clears its own roots.
- redistribute-memory.mjs: the neutered (no-op) publishBatches left the algorithm
  promoting the VM-bound cohort into SWM with no drain -> a mixed (wrong) partition.
  Now bails before any SWM mutation unless --skip-vm is set (promote-to-SWM-only),
  pending the #1260 named-KA rework.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rity)

otReviewAgent: dkg_knowledge_asset_create forwarded the quad `object` verbatim while
OpenClaw + Hermes normalize it through their write normalizers -- so a bare literal was
auto-quoted there but had to be pre-quoted in MCP (portable-agent drift). Inlined
normalizeRdfObject (replicating dkg-core's normalizeDkgPublisherObject / isDkgRdfTerm /
escapeDkgRdfLiteral verbatim, with a keep-in-sync note -- matching MCP's existing inline
validateAssertionName convention; MCP stays dep-light). The create one-shot now does
normalizeRdfObject(strip(object)) -- strip angle brackets first, then auto-type (bare
literal -> quoted; URI/blank/already-quoted pass through). Added a 6-case parity test;
updated the object schema description. The MCP write path is unchanged (pre-existing,
daemon-normalized).

tsc clean; assertion-lifecycle 55/55; parity proven byte-identical across 12 cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ysis traces

otReviewAgent: after the publishAssertion migration (r9), the analysis traces still
called traceSharedMemoryWrite before publishAssertion, which now stages the same quads
internally -- a double SWM write the ESBench path no longer does, making the measured
publish trace unrepresentative of the canonical create/write/share/publish flow. Removed
the pre-write from analyzeGetFlow + analyzeSyncPublishFlow (publishAssertion stages
internally). KEPT it in analyzeAsyncPublishFlow, which needs the returned
shareOperationId for the async publisherEnqueue/lift path.

bench tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…> /vm/publish serialization

otReviewAgent: the #1087 compatibility wrapper (publishFromFinalizedAssertion ->
/vm/publish, used by `dkg shared-memory publish`/`dkg index`/the benchmarks) was only
validated indirectly. Added 3 focused serialization tests: publishFromFinalizedAssertion
POSTs the per-KA /vm/publish route with clearAfter translated to
options.clearSharedMemoryAfter (and omits the options object when no flags are set);
publishAssertion runs the create -> /vm/publish two-call sequence. api-client 49/49.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Name + partial/error)

otReviewAgent: the publish panel was rewritten (root-entity selection -> named SWM
assertions + knowledgeAssetPublishWithSeal) but only the api helper was tested. Added a
component test that renders the panel with mocked SWM assertions, selects by graphUri,
and asserts the publish call receives the assertion name + subGraphName, plus that a 207
contextGraphError renders the partial warning and a SwmSubsetNotSealableError surfaces.
Exported PublishPanel for the test (testability only, no behavior change).

node-ui memory-layer-publish-panel 4/4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ize fixtures, dep-light)

otReviewAgent: the inline normalizeRdfObject (the dkg_knowledge_asset_create quad-object
contract) duplicates core's normalizeDkgPublisherObject/escapeDkgRdfLiteral and could
drift silently. MCP is deliberately dep-light (3 prod deps; inlines validateAssertionName
+ share-warning constants for the same reason; core's normalizer isn't exported from
core's entry), so rather than add a dependency we centralize the conformance fixtures:
- Exported normalizeRdfObject/isRdfTerm/escapeRdfLiteralBody + a new conformance test:
  24 hand-written golden cases (bare->quoted, http/https/urn/did passthrough, blank node,
  already-quoted/typed/lang-tagged, every ECHAR escape, empty) asserting the inline
  matches the documented contract (verified byte-identical to core's actual output). A
  future drift in either copy now trips a red test.
- Bidirectional keep-in-sync pointers between core's normalizeDkgPublisherObject and the
  conformance test.

tsc clean; conformance 26/26; mcp-dkg 254 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…publish warning + assert it

otReviewAgent: the publish-panel partial test mocked partialPublishWarning with an
arg-ignoring stub, so it stayed green even if the component dropped the daemon's
contextGraphError detail. Tightening the assertion (toHaveBeenCalledWith the detail)
exposed a real gap: the batch ("Publish All") path called partialPublishWarning() with
NO detail -- only a count. Now the batch captures the first 207's contextGraphError and
forwards it (partialPublishWarning(firstPartialError)) so the operator sees WHAT failed,
not just how many; the strengthened test asserts the detail is forwarded + rendered.

tsc clean; memory-layer-publish-panel 4/4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mote CTAs

otReviewAgent: entities.tsx ran ensureContextGraphOnChain unconditionally before
knowledgeAssetPublishWithSeal, defeating the /vm/publish register-then-mint contract --
on a never-registered CG a doomed publish (unsealed/subset/stale) would burn registration
gas and only THEN 409, and the same shared pre-register needlessly fired for WM->SWM
promote. Removed it from both entities handlers (handleAction + handlePromoteAll) and the
matching stale pre-register in layer-widgets' promote branch. The daemon owns the
ordering: /vm/publish checks preconditions FIRST and auto-registers only if a valid
publish's sole remaining blocker is an unregistered CG (knowledge-assets.ts:1150 ->
ensureRegisteredForPublish, idempotent + race-safe; #1116). The seal is CG-independent
(#1116: finalize works on an unregistered CG with no chain write) and promote is
off-chain, so neither path needs client-side registration.

tsc clean; ui-api-pure 54/54; publish-panel 4/4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aphUri disambiguation)

otReviewAgent: the publish-panel fixtures used distinct names (alpha/beta), so they
didn't exercise the graphUri disambiguation the selection keying exists for -- a
name-keyed regression would still pass. Added a case with the SAME name in both a root
and a sub-graph row: selecting only the sub-graph row publishes exactly one KA
('shared', {subGraphName:'sg1'}) and never the same-named root, which a name-keyed
selection (matching both) would fail.

publish-panel 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RDF root URN

otReviewAgent: the get + sync ESBench flows built the publishAssertion name as
esbench-<flow>-${payload.rootEntity}, but rootEntity is an RDF URN
(urn:dkg:benchmark:...:${runId}:...) whose ':' / '/' characters validateAssertionName
rejects -- so after the publishAssertion migration the bench would throw before measuring
on any CG id with the normal <curator>/<slug> form. Both flows now derive the name from
the safe run id (esbench-get-<seq> / esbench-sync-<seq>) -- the same slug already passed
to createPayload -- threaded via a syncName var for the sync flow whose publish runs in
the measured fn. The read still queries by rootEntity (unchanged); analyze-publish-async-
get was already slug-based.

bench tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
otReviewAgent: the sync benchmark's beforeIteration still called sharedMemoryWrite even
though the measured publishAssertion stages the same quads internally -- a double SWM
write that the analyze runner (and the get/read flow) already dropped, leaving the suite
exercising both the retired loose-SWM write and the new named-KA composite. Removed the
sync pre-write so the measured publish reflects the canonical create/write/share/publish
flow. The async flow keeps its write -- it needs the returned shareOperationId for the
publisher enqueue/lift path.

bench tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… publish-ready

otReviewAgent: dkg_knowledge_asset_create's one-shot claimed "shared to SWM
(publish-ready)" from the requested alsoShareSwm flag, not the daemon outcome. The create
route returns 207 + errors:[{phase:"swm-share"}] when create+seal succeeds but the opt-in
share fails, and DkgClient treats 207 as success -- so an agent could be sent to publish
an asset that never reached SWM. Now inspects result.errors/publishReady and, when the
share tail failed, returns an errResult (create+seal OK, NOT publish-ready, retry
dkg_knowledge_asset_share). Added a 207-share-failed test.

mcp tsc clean; assertion-lifecycle 56/56.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ONDITION 409

otReviewAgent: the seal-then-retry test scripted a PUBLISH_NOT_FULL_SHARE publish 409 then
a SUCCESSFUL finalize(layer:swm) retry -- an impossible daemon path, since
PUBLISH_NOT_FULL_SHARE means the full-share marker is missing and finalize(layer:swm)
checks that same marker and returns SWM_SUBSET_NOT_SEALABLE. Re-coded the recoverable case
to VM_PUBLISH_PRECONDITION (the sealable 409 -- an unsealed full share), keeping the
layer:swm + subGraphName seal-shape assertions; removed the now-redundant second
VM_PUBLISH_PRECONDITION test. The PUBLISH_NOT_FULL_SHARE -> SWM_SUBSET_NOT_SEALABLE ->
throws-no-retry case is unchanged.

node-ui tsc clean; ui-api-pure 53/53.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… in OpenClaw + Hermes (MCP parity)

otReviewAgent: r19 fixed this in MCP but the byte-parallel OpenClaw and Hermes one-shot
create paths still forwarded the daemon result as a success. The create route returns 207
+ errors:[{phase:"swm-share"}] when create+seal lands but the opt-in alsoShareSwm tail
fails, and all three adapter clients treat 207 as success -- so OpenClaw/Hermes could send
an agent to publish an asset that never reached SWM. Both now judge from the outcome
(errors/publishReady) and return a tool error (create+seal OK, NOT publish-ready, retry
dkg_knowledge_asset_share) when the share failed. Hermes extracts a testable
_create_swm_share_error helper (mirrors _annotate_share_seal). Added a share-failed test
to each adapter.

OpenClaw tsc clean + plugin 61/61; Hermes pytest 115/115.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…or commit)

The main CI matrix (ci.yml) did not fire for f86bb5d — GitHub dropped the
pull_request synchronize event. This empty commit re-fires it so the Kosava/Bura
test matrix runs and registers on the PR. No code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y with MCP + Hermes)

otReviewAgent (nit): OpenClaw only type-checked also_share_swm inside the `quads` branch,
so a bare create with also_share_swm:"yes" was silently accepted and ignored -- while MCP
and Hermes validate the shape even when ignored, and the shared tool schema advertises a
boolean. Moved the check before the quads branch so the runtime contract is uniform across
adapters. Added a bare-create boundary test.

openclaw tsc clean; plugin 61/61.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r (de-drift the CTAs)

otReviewAgent: the SWM->VM batch publish loop was hand-rolled in MemoryLayerView, entities,
and layer-widgets, and the copies had drifted -- MemoryLayerView surfaced the first
contextGraphError detail + sample while the others dropped it via partialPublishWarning().
Extracted the canonical loop into api.ts `publishAssertionsToVm` (returns one
BatchPublishResult: published/sealed/partial/partialError/sample/lastError) and routed all
three CTAs through it, so entities + layer-widgets now also render the partial detail.
Moved the component test to assert the selection->helper handoff + rendering, and added
publishAssertionsToVm forwarding/aggregation unit tests in ui-api-pure.

node-ui tsc clean; ui-api-pure 55/55; publish-panel 5/5. (The unrelated :3000-backend
store tests fail only locally with no node running, and pass in CI.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onsToVm

otReviewAgent: the shared batch helper collapsed every per-KA failure into a single
`lastError`, so callers could only say "some failed" + show the last error -- losing the
count and the names/reasons of earlier failures. Replaced `lastError` with
`failures: Array<{ name, subGraph?, error }>` (the failed count is `failures.length`). All
three CTAs now report the failed count, and MemoryLayerView surfaces the first failure's
name + reason (+N more). Updated the helper unit tests + the component test.

node-ui tsc clean; ui-api-pure 55/55; publish-panel 5/5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…easured name uniqueness

otReviewAgent: MockBenchmarkClient discarded the assertion `name`, so the warmup/measured
KA-name-uniqueness fix (runner.ts names each publish
benchmark-sync-<runId>-<warmup|measured>-<iteration>) was not validated -- a regression
reusing the warmup name for the measured publish (KA create is name-idempotent) would
still pass the root-only check. The mock now records `name` in publishCalls and the test
asserts the warmup + measured names are unique and phase-tagged.

cli tsc clean; the name-uniqueness test passes. (The one local failure is the pre-existing
comma-locale flamegraph flake; CI renders en-US.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lel across MCP/OpenClaw/Hermes

otReviewAgent: Hermes _normalize_quads diverged from the MCP/core create-object contract
(lowercase-only schemes, no blank-node, no bracket strip), so the same
dkg_knowledge_asset_create({quads}) produced different triples per runtime. Fixed Hermes
to match: _is_rdf_term (CASE-INSENSITIVE http(s)/urn/did + `_:` blank + already-quoted) +
_strip_angle_brackets on subject/predicate/object. Also closed the bracket gap the
adversarial review found in OpenClaw -- only MCP stripped `<…>`, so fixing only Hermes
would have left a new MCP+Hermes-vs-OpenClaw split; OpenClaw's create one-shot now strips
too. Added a 25-case Hermes conformance pytest (mirrors the MCP golden fixture) + an
OpenClaw bracket/parity test; MCP's existing strip test stays green. (Follow-up, out of
scope: Hermes _normalize_semantic_quads + the MCP/OpenClaw WRITE-path object handling
diverge separately.)

OpenClaw 62/62; MCP 82/82; Hermes pytest 140/140.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rve padded literals)

otReviewAgent: _normalize_quads .strip()'d subject/predicate/object before classifying,
but MCP/OpenClaw only bracket-strip (no whitespace trim) -- so a padded literal '  x  '
became '"x"' on Hermes while the other adapters preserved '"  x  "', breaking the
cross-adapter create one-shot parity the prior commit established. Removed the .strip();
kept the bracket-strip + the empty-object rejection. Added a whitespace-preservation
conformance case.

Hermes pytest 141/141.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
)

The main CI matrix did not fire on b7e8c7d — GitHub dropped the pull_request
synchronize event (recurring on this branch). This empty commit re-fires it so the
Kosava/Bura matrix runs and registers as the PR's required checks. No code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Jurij89 Jurij89 force-pushed the feat/api-agent-tooling-cleanup branch from b6fc66e to 52cedc1 Compare June 24, 2026 18:39
// VM-bound cohort into SWM expecting to drain it to VM — without that drain it
// would leave a mixed SWM partition (VM-bound + SWM-bound). So bail BEFORE any
// SWM mutation unless --skip-vm is set (promote-to-SWM-only, which is coherent).
if (!SKIP_VM) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Issue: This new early exit makes the rest of the VM-publish section unreachable: !SKIP_VM exits here, and the existing if (SKIP_VM) process.exit(0) exits at line 295. The register/publish/verify code below is now dead but still carries comments describing behavior that can never run, which makes this script harder to reason about and easy to drift. Collapse the disabled modes into explicit returns and remove or clearly isolate the unreachable publish section.

return bool(value) and any(value.startswith(prefix) for prefix in ("http://", "https://", "urn:", "did:"))


# #1265 cross-adapter create/write one-shot quad-object contract — byte-parallel with

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Nit: The comment claims this normalizer is “byte-parallel” with MCP/core, but Hermes still routes bare literals through _quote_literal, which also UCHAR-escapes remaining ASCII control bytes while the referenced MCP/core helpers only escape the ECHAR set. Either narrow the comment to the shared classification/bracket-stripping behavior or align the literal escaping contract so the parity claim is true.

* `knowledgeAssetPublishWithSeal` (seal-in-SWM retry + 207 partial handling), aggregating
* into ONE `BatchPublishResult`. The canonical batch-publish loop reused by every CTA
* (MemoryLayerView / entities / layer-widgets) so the partial-detail, sample, and per-KA
* error accounting cannot drift between them. Per-KA failures are collected into

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Nit: This JSDoc says per-KA failures are collected into lastError, but the implementation now accumulates structured entries in failures[]. Update the comment so callers and future maintainers do not look for a non-existent lastError flow.

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.

Post-rc.17: deprecate the legacy /api/shared-memory/publish bridge and consolidate the one-shot publish onto the per-KA lifecycle

2 participants