Skip to content

[AAASM-3500] ✨ (node-sdk): Wire OpControlSubscriber into withAssembly tool path#172

Merged
Chisanan232 merged 3 commits into
masterfrom
v0.0.1/AAASM-3500/feat/wire_op_control
Jun 20, 2026
Merged

[AAASM-3500] ✨ (node-sdk): Wire OpControlSubscriber into withAssembly tool path#172
Chisanan232 merged 3 commits into
masterfrom
v0.0.1/AAASM-3500/feat/wire_op_control

Conversation

@Chisanan232

Copy link
Copy Markdown
Contributor

Target

  • Task summary:

    Follow-up to Bug AAASM-3491 (live kill switch never reached a running agent). The authoritative runtime fix merged in agent-assembly PR #1176 and the python-sdk companion in python-sdk PR [AAASM-3177] ✅ (node-sdk): Raise test coverage toward 90% #156 (the reference pattern). The node-sdk already shipped a complete OpControlSubscriber in src/op-control.ts, but it was dead code: not exported from index.ts and never consulted by the withAssembly tool wrapper, which only did pre-exec gateway.check() + waitForApproval. This PR exports the subscriber and wires it into the withAssembly tool path so an operator terminate/pause reaches a running tool through the SDK layer.

  • Task tickets:

  • Key point change (optional):

    • Order is load-bearing: the op-control kill switch runs before the gateway check. A terminated op fast-fails the call (short-circuit — the gateway is never queried); a paused op blocks cooperatively in waitForOp until the gateway resumes it.
    • op_id resolves from an explicit opId on the call's first argument, otherwise composes {traceId}:{spanId}; absent a trace identity, op control is skipped.
    • The subscriber is threaded through as an optional opControl dependency on WithAssemblyOptions via a narrow OpControl seam, mirroring the Python companion's build_governance_interceptor(op_control=...). Without it, the tool path behaves exactly as before.

Effecting Scope

  • Action Types:
    • ✨ Adding new something
      • 🟢 No breaking change
  • Scopes:
    • 🧩 SDK public API
    • 🤖 Framework hooks
    • 🧪 Testing
      • 🧪 Unit testing
  • Additional description:
    Purely additive. opControl is optional; existing withAssembly callers are unaffected.

Description

  • src/wrappers/with-assembly.ts: add the OpControl seam + optional opControl option; consult op-control before the gateway check in both the execute and invoke tool paths (terminate → PolicyViolationError, pause → cooperative block); add extractOpId (opId else {traceId}:{spanId}).
  • src/wrappers/index.ts: re-export the OpControl type.
  • src/index.ts: export OpControlSubscriber, OpControlClient, OpControlSubscriberOptions, and OpTerminatedError from the package entrypoint so the kill switch is wireable.
  • tests/with-assembly-op-control.test.ts: regression tests — terminate denies BEFORE the gateway is queried (short-circuit), pause blocks then proceeds on resume, no-trace-identity skips op control, op_id resolution, no-opControl path unchanged, invoke path.

How to verify

pnpm test (320 passed / 2 skipped), pnpm typecheck, pnpm lint, pnpm build — all green locally.

Closes AAASM-3500.

🤖 Generated with Claude Code

Chisanan232 and others added 3 commits June 20, 2026 15:57
Consult the live OpControlSubscriber before the pre-exec gateway check in
the withAssembly tool wrapper (AAASM-3491). A terminated op fast-fails the
call before the gateway is queried (short-circuit); a paused op blocks
cooperatively in waitForOp until the gateway resumes it. The subscriber is
an optional `opControl` dependency threaded through WithAssemblyOptions via
the narrow OpControl seam, mirroring the Python companion's
build_governance_interceptor(op_control=...). op_id resolves from an explicit
opId or composes {traceId}:{spanId}; absent a trace identity op control is
skipped. Closes the gap where an operator terminate/pause never reached a
running tool through the SDK path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OpControlSubscriber (and OpTerminatedError / OpControl seam) shipped in
src/op-control.ts but were unreachable from `@agent-assembly/sdk` — callers
could not construct a subscriber to pass to withAssembly. Re-export them from
the public entrypoint so the AAASM-3491 kill switch is wireable (AAASM-3500).

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

Cover the AAASM-3491 wiring: a terminate denies the tool BEFORE the gateway
is queried (short-circuit asserted via gateway.check not called), a paused op
blocks then proceeds on resume, a call without trace identity skips op control,
and op_id resolution (explicit opId wins, else {traceId}:{spanId}). Mirrors the
Python companion's test_runtime_interceptor suite.

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

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/wrappers/with-assembly.ts 88.57% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@sonarqubecloud

Copy link
Copy Markdown

@Chisanan232

Copy link
Copy Markdown
Contributor Author

🔎 Review (Claude Code) — ready for approval

CI: ✅ All green — 16/16 checks pass, 0 failures (Analyze/CodeQL/SonarCloud, build ESM+CJS, vitest, typecheck, lint). pnpm test 320 passed locally.

Scope vs AAASM-3500 ("Export + wire OpControlSubscriber in node-sdk withAssembly tool path"): ✅ Fully covers it. The previously-dead OpControlSubscriber (+ OpControlClient, OpTerminatedError) is now exported from the package entrypoint, and withAssembly consults op-control in enforceOpControl before the pre-exec gateway.check() on both the execute and invoke paths: a terminated op short-circuits (throws, gateway never queried), a paused op blocks cooperatively in waitForOp until resume, a call with no trace identity skips op-control, and op_id resolves from an explicit value or {traceId}:{spanId}. 6 regression tests assert exactly these contract points. The OpControl seam is structural so the concrete subscriber satisfies it without a hard import.

Additive & safe: opControl is optional — existing withAssembly callers are unaffected (gateway-check + approval only, unchanged). Auto-constructing the subscriber inside initAssembly is intentionally deferred (matches the python-sdk #156 companion's "callers opt in" scope) — not a gap.

Verdict: Scope complete, CI green, regression coverage present. Ready to approve & merge. Follow-up AAASM-3500 stays open until merge + re-verification.

@Chisanan232 Chisanan232 merged commit a07c6c9 into master Jun 20, 2026
16 checks passed
@Chisanan232 Chisanan232 deleted the v0.0.1/AAASM-3500/feat/wire_op_control branch June 20, 2026 08:15
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.

1 participant