Skip to content

Agent SDK: merge Agentspan + porting-spec conformance#127

Closed
kowser-orkes wants to merge 10 commits into
mainfrom
feature/agent-guide-conformance
Closed

Agent SDK: merge Agentspan + porting-spec conformance#127
kowser-orkes wants to merge 10 commits into
mainfrom
feature/agent-guide-conformance

Conversation

@kowser-orkes

@kowser-orkes kowser-orkes commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Pull Request type

  • Bugfix
  • Feature
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • WHOSUSING.md
  • Other (please describe):

Changes in this PR

What

  • merge Agentspan agent SDK: 4 new modules — conductor-ai, conductor-ai-spring, conductor-ai-examples, conductor-ai-e2e (-Pe2e gated)
    • publishes org.conductoross:conductor-ai[-spring], supersedes conductor-agent-sdk 0.1.0
  • agent control plane → conductor-client: AgentClient interface + OrkesAgentClient, 11 /agent/* ops, via OrkesClients.getAgentClient()
  • SSE: connect failure throws SSEUnavailableException; reconnect w/ Last-Event-ID
  • verbs: serve = deploy + serve, serve(false, ...) non-blocking; AgentHandle send (now actually delivers) / stop / pause / resume / cancel / getStatus
  • RunSettings per-run LLM overrides
  • env: CONDUCTOR_* wins over AGENTSPAN_*; default server 8080 (was 6767); 6 AgentConfig knobs
  • worker credentials via runtimeMetadata wire contract, fail-closed; /workers/secrets fetch path deleted — see docs/design/secret-injection-contract.md
  • liveness: unpolled SCHEDULED task → WorkerStallError
  • swarm: transfer tools echo message; check_transfer first-wins + dropped_transfers
  • agent-e2e CI matrix: conductor-oss 3.32.0-rc.8 + agentspan 0.4.4

Kowser and others added 9 commits July 9, 2026 19:07
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>
Comment thread .github/workflows/agent-e2e.yml Fixed
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ Complexity Δ
...ss/conductor/ai/spring/AgentAutoConfiguration.java 100.00% <ø> (ø) 4.00 <0.00> (?)
...conductoross/conductor/ai/spring/AgentCatalog.java 92.30% <ø> (ø) 19.00 <0.00> (?)
...ductoross/conductor/ai/spring/AgentProperties.java 100.00% <ø> (ø) 5.00 <0.00> (?)
...main/java/org/conductoross/conductor/ai/Agent.java 72.24% <ø> (ø) 68.00 <0.00> (?)
...ava/org/conductoross/conductor/ai/AgentConfig.java 90.00% <ø> (ø) 30.00 <0.00> (?)
...va/org/conductoross/conductor/ai/AgentRuntime.java 25.40% <ø> (ø) 64.00 <0.00> (?)
...org/conductoross/conductor/ai/CallbackHandler.java 0.00% <ø> (ø) 0.00 <0.00> (?)
...ava/org/conductoross/conductor/ai/RunSettings.java 81.25% <ø> (ø) 12.00 <0.00> (?)
...g/conductoross/conductor/ai/enums/AgentStatus.java 100.00% <ø> (ø) 1.00 <0.00> (?)
...org/conductoross/conductor/ai/enums/EventType.java 88.23% <ø> (ø) 2.00 <0.00> (?)
... and 66 more

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kowser-orkes kowser-orkes changed the title Feature/agent guide conformance Agent SDK: merge Agentspan + porting-spec conformance Jul 14, 2026
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>
@kowser-orkes kowser-orkes marked this pull request as draft July 14, 2026 04:28
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.

2 participants