Toggle native secret mechanism on agentspan.embedded; host-delivered secrets when embedded#307
Open
NicholasDCole wants to merge 10 commits into
Open
Toggle native secret mechanism on agentspan.embedded; host-delivered secrets when embedded#307NicholasDCole wants to merge 10 commits into
NicholasDCole wants to merge 10 commits into
Conversation
…ed; host-delivered secrets when embedded
Keep AgentSpan's full native credential mechanism and gate it on the
`agentspan.embedded` feature flag instead of deleting it:
- Standalone (embedded=false): native mechanism active, unchanged (encrypted
store, execution-token minting, /api/workers/secrets pull, SDK fetchers).
- Embedded (embedded=true): native beans dormant; the host resolves secrets via
`${workflow.secrets.NAME}` (conductor-oss PR #1255 substituteSecrets/SecretsDAO).
Server:
- Part A: @ConditionalOnProperty(agentspan.embedded=false, matchIfMissing=true)
on all native secret beans (WorkerController, CredentialResolutionService,
ExecutionTokenService, CredentialAwareMcpService, CredentialMaskingResponseAdvice,
store/masterkey/seeder/migrator/datasource, NoOpSecretOutputMasker). Widely-injected
consumers made tolerant (AgentspanAIModelProvider via ObjectProvider + guards).
- Part B: worker tools stamp inputParameters.__resolved_credentials__ =
{NAME: "${workflow.secrets.NAME}"} (embedded only, via the enrich script);
LLM apiKey stamped ${workflow.secrets.<PROVIDER_KEY>} (LlmProviderEnv).
SDKs: each worker prefers host-delivered __resolved_credentials__ from task input,
falling back to the native token-pull (standalone). No conductor client-library
rebuilds — the map rides in the preserved inputData.
Tests (fail-first validated): NativeSecretGatingTest, ToolCompilerWorkerCredTest,
ReadResolvedCredentialsTest (Java), test_resolved_credentials.py, TS credentials tests.
Conductor pinned to a local runtimemeta build (PR #1255) until it ships.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…al runtimemeta pin)
AgentSpan references no PR #1255 API (it only emits ${workflow.secrets.NAME}
strings; the embedded host resolves them at runtime via substituteSecrets/SecretsDAO),
so it builds against the published conductor. The local 3.32.0-rc.3-runtimemeta-LOCAL
build added a conductor-side SecretResource that shadowed agentspan's SecretController
GET /api/secrets, breaking CredentialControllerTest in standalone. Reverting the pin
fixes it; the full server suite is green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the server-tests CI failure (:spotlessJavaCheck) — reformat ToolCompiler and the two new test classes to the project's spotless style. No logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r workers, ${workflow.secrets} for system tasks
State the intended split explicitly: worker tools use TaskDef.runtimeMetadata
(host resolves onto wire-only Task.runtimeMetadata at poll); system tasks (LLM
apiKey, HTTP/MCP/planner headers) use ${workflow.secrets.NAME}. Add a per-SDK
table specifying exactly what the `Task` model change is in each conductor client
library (java-sdk, python-sdk, csharp-sdk, orkes TS SDK) required to adopt the
target, plus a migration checklist. Document the shipped __resolved_credentials__
path as the interim, client-compatible worker delivery until those clients ship
the runtimeMetadata field.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gram + secret-resolution demo - Rewrite the design doc to be concise: split-by-task-type delivery, interim enrichment sequence diagram + caveat, target-vs-interim (safety, not perf), per-SDK Task.runtimeMetadata change table, and a single status table. - Add sdk/python/examples/demo_secret_resolution.py — a minimal 1-tool + LLM agent that reads DEMO_SECRET via get_secret(); doubles as a CI smoke check (exit 1 unless the secret resolved and the workflow completed). Validated end-to-end on local Orkes via SDK run and webhook trigger. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nce diagrams Expand the migration section into a concrete file-by-file breakdown of the target change (AgentService.setRuntimeMetadata + removal of the enrich stamping; per-SDK read swap to task.runtimeMetadata; client dep bumps). Add a labeled target-state sequence diagram alongside the interim one so the two delivery paths can be compared directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…integration Embedded LLM auth is the host's job: OrkesAIModelProvider (@primary) resolves the model per call from the AI integration by (orgId, integrationName) and never reads an apiKey from task input — so agentspan's ${workflow.secrets.<PROVIDER_KEY>} stamping was redundant and bypassed, and the hard-coded provider->env-var map was a smell. - Delete LlmProviderEnv and AgentChatCompleteTaskMapper.injectCredentialReferences. - Remove the dead embedded readResolvedApiKeyFromTaskInput branch in AgentspanAIModelProvider. - Worker-tool + HTTP/MCP header secret delivery (${workflow.secrets}) unchanged; standalone per-user LLM key resolution unchanged. - Doc: correct the LLM sequence diagram to the real OrkesAIModelProvider/integration flow (verified in orkes-conductor) and update the delivery/implementation/migration sections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n end-state
Fix the two spots that still lumped LLM keys into the ${workflow.secrets} system-task
path: system tasks are LLM (host AI integration) + HTTP/MCP/planner headers (${workflow.secrets}).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 10, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AgentSpan keeps its full native credential mechanism and gates it behind the
agentspan.embeddedfeature flag instead of deleting it (an earlier abandoned branch had ripped itout). One flag decides who delivers secrets:
agentspan.embeddedfalse(default)POST /api/workers/secretspull, SDK fetchers. Unchanged frommain.trueDesign doc (full detail + sequence diagrams):
design/2026-07-09-embedded-secret-toggle-design.md.How secrets are delivered when embedded (split by task type)
LLM_CHAT_COMPLETEresolves its model and API key from the configured AI integration by provider name
(
OrkesAIModelProvider→getIntegration→api_key); agentspan stamps nothing. Verified inorkes-conductor. (We deliberately do not map a provider to a conventionally-named workflowsecret — that would duplicate and can conflict with the integration.)
${workflow.secrets.NAME}(the user's external-APIsecrets). The compiler rewrites
${NAME}→${workflow.secrets.NAME}when embedded; the hostsubstitutes in memory before the in-process call.
target is
TaskDef.runtimeMetadata(declare names → host injects values onto the wire-onlyTask.runtimeMetadata). Until the conductor client libraries expose that field, this PR shipsan interim, client-compatible path: stamp
inputParameters.__resolved_credentials__ = { NAME: "${workflow.secrets.NAME}" }, which the hostresolves via PR #1255's
substituteSecrets— it rides in the task-inputMapevery client alreadykeeps. Same runtime cost; the target is strictly better on persistence-safety (the migration + the
per-client
Task.runtimeMetadatachange are documented in the design doc).Server changes
@ConditionalOnProperty(name="agentspan.embedded", havingValue="false", matchIfMissing=true)on all native secret beans (
WorkerController,CredentialResolutionService,ExecutionTokenService,CredentialAwareMcpService,CredentialMaskingResponseAdvice,EncryptedDbCredentialStoreProvider,MasterKeyConfig,CredentialEnvSeeder,CredentialSchemaMigrator,CredentialDataSourceConfig,NoOpSecretOutputMasker; joins the already-gatedSecretController/CredentialAwareHttpTaskConfig).Active consumers made tolerant:
AgentspanAIModelProviderviaObjectProvider+ guards;AgentService/AgentEventListener@Autowired(required=false)+ null guards (token minting skipped).injectCredentialReferences+LlmProviderEnvapiKey-stamping was redundant and bypassed byOrkesAIModelProvider, so it was removed.)ToolCompiler.buildWorkerCredConfig+JavaScriptBuilderenrich-scriptinjection +
AgentCompiler.collectToolCredentialsstamp__resolved_credentials__onto SIMPLE tasks.SDK read-path (all four) — auto-detect
Each worker prefers the host-delivered map from task input, else falls back to the native token-pull
(standalone); resolved values feed the existing accessor/injection machinery unchanged; the key is
stripped. Native fetcher code is kept. Interim reads
inputData.__resolved_credentials__; the targetreads
task.runtimeMetadata.worker.ts/credentials.ts· Java —WorkerManager.readResolvedCredentials· Python —
_dispatch.py· C# —WorkerManager.ReadResolvedCredentials.Dependency
AgentSpan references no PR #1255 API, so it compiles/tests against the published
conductor 3.32.0-rc.3.${workflow.secrets.NAME}is resolved at runtime by the embedded host(which must include PR #1255). No SDK client-library rebuilds for the interim.
Tests (fail-first validated)
NativeSecretGatingTest— native beans present standalone, absent embedded.ToolCompilerWorkerCredTest— GraalJS-runs the enrich script; SIMPLE task carries__resolved_credentials__when embedded, nothing standalone.ReadResolvedCredentialsTest(Java),test_resolved_credentials.py(Python), TScredentials.test.ts.Not covered here
TaskDef.runtimeMetadatais documented but not implemented — blocked on the conductorclient libraries exposing the field (see the design doc's per-client table + migration).
🤖 Generated with Claude Code
Test Evidence:
Orkes Conductor:
Manual Trigger
LLM Calls working (System Task OPEN AI API KEY Resolved from Integrations)
Task call working (worker receiving demo secret input in plain text)

Webhook Trigger
LLM Calls working (System Task OPEN AI API KEY Resolved from Integrations)
Task call working (worker receiving demo secret input in plain text)
