Agent SDK: merge Agentspan + porting-spec conformance#127
Closed
kowser-orkes wants to merge 10 commits into
Closed
Agent SDK: merge Agentspan + porting-spec conformance#127kowser-orkes wants to merge 10 commits into
kowser-orkes wants to merge 10 commits into
Conversation
Four new Gradle modules carrying the agent SDK from agentspan-ai/agentspan sdk/java @ f8704fa3: - conductor-ai core SDK (107 main + 29 test files) - conductor-ai-spring Boot 3 auto-configuration (3 main + 2 test) - conductor-ai-e2e 26 e2e suites, all tasks gated behind -Pe2e - conductor-ai-examples 158 runnable examples (-PmainClass runner) Sources are copied verbatim; the only churn is a mechanical spotlessApply conforming them to the repo conventions (Apache header, Conductor import order). Build files clone the shapes of conductor-client-metrics, conductor-client-spring, tests, and harness respectively; conductor-ai[-spring] publish via publish-config as org.conductoross:conductor-ai[-spring]. Upstream docs land under docs/agents/ (content pass to follow). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Modeled on the python-sdk agent-e2e action. Downloads the pinned agentspan-server-0.4.0.jar release asset (cached), boots it on :8080 with the LLM repo secrets in the server env only, starts mcp-testkit, then runs ./gradlew :conductor-ai-e2e:test -Pe2e. A guard parses the JUnit XML and fails on zero executed tests, because BaseTest assumeTrue-skips every suite when the server is unreachable — without it a boot failure would green-wash the job. Fork PRs (no secrets) fail at the guard by design. Needs OPENAI_API_KEY and ANTHROPIC_API_KEY repo secrets before it can go green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docs/agents/: dependency snippets now point at org.conductoross:conductor-ai[-spring]:5.1.0 (was org.conductoross.conductor:conductor-agent-sdk[-spring]:0.1.0); license note switched to Apache 2.0 (repo LICENSE); one source path updated to conductor-ai/src. Java package references are unchanged (the package itself did not move). - Root README: Durable AI Agents blurb in the AI & LLM Workflows section and an AI Agents row in the Documentation table. - CHANGELOG: Unreleased entry for the merge, coordinate supersession note, and the new agent-e2e workflow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AgentClient (compile/deploy/start/status/respond) and the SSE streaming client SseClient move from conductor-ai's internal package to io.orkes.conductor.client; OrkesClients gains getAgentClient(). Their transport DTOs (AgentRequest, StartResponse, AgentStatusResponse, PendingTool, RespondBody, CompileResponse) land in io.orkes.conductor.client.model.agent, and the typed exceptions (AgentspanException, AgentAPIException, AgentNotFoundException) in io.orkes.conductor.client.exceptions — the 404 -> AgentNotFoundException mapping stays on the client (SuiteHttpApi404 contract). Boundary adaptations, since conductor-client cannot see conductor-ai types: - AgentRequest is now a pure JSON transport DTO (@JsonInclude NON_NULL, no custom serializer). Domain serialization stays in conductor-ai: AgentRuntime.agentRequest() calls AgentConfigSerializer.serialize() and resolves the Framework wire name at build time; static plans are passed as plan.toJson(). - SseClient is transport-only and emits raw parsed JSON maps ([DONE] and type=done handling unchanged); AgentStream maps them to AgentEvent, keeping the log-and-skip contract for unmappable events. - Credential* exceptions stay in conductor-ai, still extending the (relocated) AgentspanException base. Tests for the moved exceptions relocate to conductor-client (AgentExceptionsTest); docs/agents and CHANGELOG updated (migration is a coordinate swap plus the relocated imports where used directly). Validation: spotlessCheck (incl. -Pe2e) green; root test green (conductor-client 987 incl. +3 relocated, conductor-ai 271/271 with live server incl. 10 schedule integration tests, 0 failures); live e2e vs agentspan-server-0.4.0: 138 tests, 137 executed, 0 failed (1 env-gated media skip, same as baseline); Example01 smoke COMPLETED; javadoc builds with no new warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…control-plane surface (spec R1/R5)
Split the concrete AgentClient into an interface (io.orkes.conductor.client.AgentClient)
plus an Orkes implementation (http.OrkesAgentClient), matching the SDK's
SchedulerClient/OrkesSchedulerClient convention. OrkesClients.getAgentClient()
now returns the interface.
New control-plane operations (Python-SDK parity, porting spec R1):
- getExecution(id) GET /agent/execution/{id} (wire-faithful Map)
- listExecutions(params) GET /agent/executions (query passthrough)
- stopAgent(id) POST /agent/{id}/stop (no body)
- signalAgent(id, message) POST /agent/{id}/signal ({"message": ...})
- streamSse(id, lastEventId) factory for a connected SseClient
- close() no-op (shared ConductorClient owns transports)
SseClient upgrades: synchronous initial connect throwing SSEUnavailableException
when the server rejects streaming (never a silently-empty stream); id: frames
tracked; mid-stream drops reconnect with bounded backoff sending Last-Event-ID.
AgentRuntime constructs OrkesAgentClient and exposes getClient() — the same
instance the runtime uses for every /agent/* call (spec R5 acceptance).
Fork point: feature/combine-agentspan @ b261ff7.
Design: idea-12/design-java.md DD4 (interface + 11 ops), DD11 (R2 satisfied by
construction via ApiClient.buildCall — no token accessor added).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ifecycle, RunSettings, CONDUCTOR_* env chain (spec R3/R4/R8/R9)
serve = deploy + serve (R9): each served agent is deployed (idempotent) before
workers start, so a bare serve(agent) is a complete deployment. New
serve(Boolean blocking, ...) overloads — Boolean, not boolean, so the overload
stays strictly more specific than the serve(Object...) drop-in (a primitive
flag made serve(false, agent) ambiguous); blocking=false returns once workers
are polling.
AgentHandle lifecycle (Python parity): send() now delivers {"message": ...}
via respond (the placeholder silently discarded the message); stop() = graceful
control-plane stop + best-effort signal unblock; pause()/resume() via the
standard workflow endpoints (javadoc disambiguates handle.resume un-pause vs
AgentRuntime.resume re-attach); cancel(reason) terminates; getStatus() returns
the server snapshot (AgentStatusResponse — the Java analogue of Python's
AgentStatus dataclass; the plan's AgentStatus enum is terminal-only and cannot
represent a running execution).
Env chain (R3): CONDUCTOR_SERVER_URL → AGENTSPAN_SERVER_URL → default, same
chain for AUTH_KEY/AUTH_SECRET; default host 6767 → 8080 at both resolution
sites; blank values never clobber the chain; injectable env seam for tests.
AgentConfig (R4): +autoStartWorkers/daemonWorkers/streamingEnabled/
livenessEnabled/livenessStallSeconds/livenessCheckIntervalSeconds with spec
defaults + env names and lenient parsing (invalid/empty → default — fixes the
NumberFormatException wart). autoStartWorkers gates worker registration in
startAsync; streamingEnabled + SSEUnavailableException degrade stream() to a
polling-mode AgentStream (getResult polls the real status — never a
silently-empty COMPLETED); liveness knobs land before their S4 reader.
autoRegisterIntegrations skipped (dead flag, design DD8).
RunSettings (R8): builder-style per-run overrides (model/temperature/maxTokens/
reasoningEffort/thinkingBudgetTokens → thinkingConfig; no topP) applied to the
serialized root agentConfig at the choke point with a != null gate (zero values
honored); overloads on run/start/stream + async twins; drop-in Object varargs
extract RunSettings before tool coercion (else it would be swallowed as a tool).
streamAsync now rides AgentClient.streamSse (runtime transport = the exposed
client, R5/T8). conductor-ai gains a mockwebserver test dep pinned to 5.3.2 —
google-adk forces okhttp 5.x onto the test classpath, versions.okHttp (4.x)
NoClassDefFounds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…closed; retire /workers/secrets fetcher; e2e matrix conductor-oss 3.32.0-rc.8 + agentspan 0.4.4 (spec R6/R12) Credentials now ride the wire contract (conductor-oss PR #1255): declared secret names are stamped on TaskDef.runtimeMetadata at registration; a capable host (agentspan > 0.4.2 / conductor-oss >= 3.32.0-rc) resolves them at poll time and delivers values on the wire-only Task.runtimeMetadata map. - TaskDef gains List<String> runtimeMetadata (declared names); Task gains Map<String,String> runtimeMetadata (delivered values); SerDer templates extended so the round-trip covers the new wire fields. - WorkerManager: the task-def upsert now runs on EVERY register() call — the old !isNew early-return skipped it, which is exactly the re-register-wipes- the-stamp bug the porting spec calls out. Upsert is PUT-overwrite first (mirrors the Python task runner) with a create fallback. - Dispatch is fail-closed: declared-but-undelivered names fail the task FAILED_WITH_TERMINAL_ERROR, naming the missing names and the server capability; ambient process env is NEVER read (proven with PATH in tests). CredentialContext (thread-local, per-call) unchanged — only the source moved. - Deleted: WorkerCredentialFetcher (POST /workers/secrets + execution token) and the fetch-transport exceptions CredentialAuthException/ CredentialRateLimitException/CredentialServiceException. CredentialNotFoundException stays (public ToolContext.getCredential API). T17 grep clean: no workers/secrets, no execution-token handling in main. - agent-e2e workflow is now a two-leg matrix per idea.md Clarifications: conductor-oss server boot JAR 3.32.0-rc.8 (Maven Central) + agentspan release JAR bumped 0.4.0 → 0.4.4. Suite2 gains a capability probe (register TaskDef with runtimeMetadata, read it back) that skips the wire-delivery suite on servers that drop the field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… contract (spec R11/R13)
Liveness (R11): stateful runs enqueue worker tasks under a per-execution
domain — if the owning process stops polling, nothing else ever will and a
wait burns its full timeout on a dead queue. startAsync now attaches a
ServerLivenessMonitor (daemon per config.daemonWorkers) to stateful runs when
livenessEnabled: every livenessCheckIntervalSeconds it fetches the workflow;
a SCHEDULED task with pollCount == 0 older than livenessStallSeconds records
a stall, which AgentHandle.waitForResult surfaces as WorkerStallError on its
next poll iteration. Monitoring stops on first stall, terminal status, or
close; the handle closes the monitor on every wait exit. Transient poll
errors are ignored — liveness never fails a healthy run.
Swarm transfers (R13): {source}_transfer_to_{peer} workers now echo the
hand-off message ({"message": ...}) instead of discarding it into emptyMap;
{name}_check_transfer scans ALL matching tool calls — first wins
(transfer_to + transfer_message from inputParameters.message, tolerating the
"arguments" variant), every non-winning transfer lands in dropped_transfers
(only when > 1) with a warning naming honored and dropped targets. Both
handlers extracted to package-private statics (the inline lambdas were
untestable in isolation); shapes mirror the Python reference byte-for-byte.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… default + verb-contract notes - CHANGELOG [Unreleased] describes the final agent surface in one place: AgentClient interface via OrkesClients.getAgentClient() with the full control plane, SSE hardening (SSEUnavailableException, Last-Event-ID reconnect), serve = deploy + serve + non-blocking variant, handle lifecycle verbs, RunSettings, CONDUCTOR_* env chain + 8080 default, runtimeMetadata credentials (fail-closed, fetch path deleted), liveness, swarm hand-off notes, and the two-server e2e matrix (conductor-oss 3.32.0-rc.8 + agentspan 0.4.4). - NEW docs/design/secret-injection-contract.md — the doc WorkerManager and ToolContext javadoc have referenced all along now exists, describing the declare → stamp → deliver → inject contract, the fail-closed rule, what replaced the /workers/secrets fetch path, and server capability. - docs/agents/*: port 6767 → 8080 everywhere; env tables document the CONDUCTOR_* → AGENTSPAN_* fallback chain and the new AgentConfig knobs; serve section documents deploy-first semantics and the non-blocking form; run section documents RunSettings. - Test-infra defaults follow the SDK default: BaseTest (e2e) and ScheduleIntegrationTest fall back to localhost:8080 (were 6767). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workflow declared no permissions block, so jobs got the default token grant. It only uses the token to download the public agentspan release JAR, so contents:read is sufficient. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Pull Request type
Changes in this PR
What
conductor-ai,conductor-ai-spring,conductor-ai-examples,conductor-ai-e2e(-Pe2egated)org.conductoross:conductor-ai[-spring], supersedesconductor-agent-sdk 0.1.0conductor-client:AgentClientinterface +OrkesAgentClient, 11/agent/*ops, viaOrkesClients.getAgentClient()SSEUnavailableException; reconnect w/Last-Event-IDserve= deploy + serve,serve(false, ...)non-blocking;AgentHandlesend(now actually delivers) /stop/pause/resume/cancel/getStatusRunSettingsper-run LLM overridesCONDUCTOR_*wins overAGENTSPAN_*; default server8080(was6767); 6AgentConfigknobsruntimeMetadatawire contract, fail-closed;/workers/secretsfetch path deleted — seedocs/design/secret-injection-contract.mdSCHEDULEDtask →WorkerStallErrormessage;check_transferfirst-wins +dropped_transfersagent-e2eCI matrix: conductor-oss3.32.0-rc.8+ agentspan0.4.4