Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
121 changes: 121 additions & 0 deletions .github/workflows/agent-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
name: Agent E2E

# Runs the agent e2e suites (conductor-ai-e2e/) against the released Agentspan
# server JAR — a full Conductor server with the agent runtime baked in.
#
# These tests call real LLMs via the OPENAI_API_KEY / ANTHROPIC_API_KEY
# repo secrets. The suites themselves never read the keys (asserted by
# Suite2ToolCallingCredentials) — only the server process gets them.
# Fork PRs cannot see repo secrets, so for them the run fails at the
# silently-empty guard rather than passing vacuously.

on: [pull_request, workflow_dispatch]

concurrency:
group: agent-e2e-${{ github.ref }}
cancel-in-progress: true

env:
AGENTSPAN_VERSION: "0.4.0" # pinned server release — bump deliberately

jobs:
agent-e2e:
runs-on: ubuntu-latest
timeout-minutes: 45
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
AGENTSPAN_SERVER_URL: http://localhost:8080/api
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'

# Python is only needed for mcp-testkit and the XML guard.
# No `cache: pip` — it hard-fails without a requirements file.
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Cache server JAR
id: jar_cache
uses: actions/cache@v4
with:
path: agentspan-server.jar
key: agentspan-server-${{ env.AGENTSPAN_VERSION }}

- name: Download server JAR from release
if: steps.jar_cache.outputs.cache-hit != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release download "v${AGENTSPAN_VERSION}" --repo agentspan-ai/agentspan \
--pattern "agentspan-server-${AGENTSPAN_VERSION}.jar" --output agentspan-server.jar

- name: Install mcp-testkit
run: |
python -m pip install --upgrade pip
pip install mcp-testkit

- name: Start mcp-testkit
run: |
mcp-testkit --transport http --port 3001 &
sleep 2

- name: Start server
run: |
java -jar agentspan-server.jar --server.port=8080 > server.log 2>&1 &

- name: Wait for server health
run: |
for i in $(seq 1 45); do
if curl -sf http://localhost:8080/health | grep -q '"healthy"[[:space:]]*:[[:space:]]*true'; then
echo "server healthy after ~$((i*2))s"; exit 0
fi
sleep 2
done
echo "::error::server failed to become healthy within 90s"
tail -100 server.log
exit 1

- name: Run e2e suites
run: ./gradlew :conductor-ai-e2e:test -Pe2e

# BaseTest assumeTrue-skips every suite when the server is unreachable —
# without this guard a boot failure after the health gate (or a future
# gate regression) would yield a green job that ran nothing.
- name: Guard against silently-empty runs
if: always()
run: |
python - <<'EOF'
import glob
import sys
import xml.etree.ElementTree as ET

total = executed = 0
for path in glob.glob("conductor-ai-e2e/build/test-results/test/TEST-*.xml"):
root = ET.parse(path).getroot()
t = int(root.get("tests", 0))
sk = int(root.get("skipped", 0))
total += t
executed += t - sk
print(f"executed {executed}/{total} tests")
sys.exit(0 if executed > 0 else 1)
EOF

- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: agent-e2e-results
path: |
conductor-ai-e2e/build/test-results/test/
conductor-ai-e2e/build/reports/tests/test/
server.log
retention-days: 14

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +23 to +121
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- Merged the Agentspan agent SDK into this repository as four new modules: `conductor-ai` (durable AI agents — `Agent`, `AgentRuntime`, `@Tool` functions, guardrails, handoffs, multi-agent strategies), `conductor-ai-spring` (Spring Boot auto-configuration), `conductor-ai-examples` (150+ runnable examples), and `conductor-ai-e2e` (e2e suites, gated behind `-Pe2e`) — docs under [docs/agents/](docs/agents/index.md)
- `conductor-ai` and `conductor-ai-spring` publish as `org.conductoross:conductor-ai` and `org.conductoross:conductor-ai-spring`, superseding `org.conductoross.conductor:conductor-agent-sdk[-spring]@0.1.0`; the java package `org.conductoross.conductor.ai[.spring]` is unchanged, so migrating is a dependency-coordinate swap only
- `agent-e2e` GitHub workflow running the e2e suites against the released `agentspan-server-0.4.0.jar`

## [5.1.0]

### Added
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,10 @@ workflowClient.restartWorkflow(workflowId, false);

Conductor supports AI-native workflows including agentic tool calling, RAG pipelines, and multi-agent orchestration.

**Durable AI Agents**

The `conductor-ai` module is a full agent SDK on top of Conductor: `Agent`, `AgentRuntime`, `@Tool` functions, guardrails, handoffs, and multi-agent strategies, with Spring Boot auto-configuration in `conductor-ai-spring` and 150+ runnable examples in `conductor-ai-examples`. Start with the [agent docs](docs/agents/index.md).

**Agentic Workflows**

Build AI agents where LLMs dynamically select and call Java workers as tools. All agentic examples live in [`AgenticExamplesRunner.java`](examples/src/main/java/io/orkes/conductor/sdk/examples/agentic/AgenticExamplesRunner.java) — a single unified runner.
Expand Down Expand Up @@ -542,6 +546,7 @@ End-to-end examples covering all APIs for each domain:
| [Conductor Client](conductor-client/README.md) | HTTP client library documentation |
| [Client Metrics](conductor-client-metrics/README.md) | Prometheus metrics collection |
| [Spring Integration](conductor-client-spring/README.md) | Spring Boot auto-configuration |
| [AI Agents](docs/agents/index.md) | Durable AI agent SDK (`conductor-ai`) guide |
| [Examples](examples/README.md) | Complete examples catalog |

## Support
Expand Down
49 changes: 49 additions & 0 deletions conductor-ai-e2e/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// End-to-end suites for the agent SDK. They need a live agentspan server
// (AGENTSPAN_SERVER_URL) plus server-side LLM credentials, so — like the `tests`
// module — every task is gated behind a property: run with -Pe2e.

plugins {
id 'jacoco'
}

dependencies {
testImplementation project(':conductor-ai')

// test dependencies
testImplementation "org.junit.jupiter:junit-jupiter-api:${versions.junit}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${versions.junit}"
testImplementation "ch.qos.logback:logback-classic:1.5.32"

// LLM frameworks: not imported by the suites directly, but the SDK's bridge
// classes (compileOnly there) need them on the runtime classpath when the
// framework-facing suites execute.
testImplementation "dev.langchain4j:langchain4j:${versions.langchain4j}"
testImplementation "dev.langchain4j:langchain4j-open-ai:${versions.langchain4j}"
testImplementation "com.google.adk:google-adk:${versions.googleAdk}"
testImplementation "org.bsc.langgraph4j:langgraph4j-core:${versions.langgraph4j}"
testImplementation "org.bsc.langgraph4j:langgraph4j-agent-executor:${versions.langgraph4j}"
}

// tool/agent parameter names are read reflectively at runtime
compileTestJava.options.compilerArgs << '-parameters'

test {
useJUnitPlatform()
finalizedBy jacocoTestReport // report is always generated after tests run
testLogging {
events = ["SKIPPED", "FAILED"]
exceptionFormat = "full"
showStandardStreams = true
}
}

tasks.withType(Test) {
// e2e suites are I/O-bound (LLM calls) and use unique agent/task names,
// so they can safely run concurrently.
maxParallelForks = 3
}

jacocoTestReport {
dependsOn test // tests are required to run before generating the report
}
tasks.forEach(task -> task.onlyIf { project.hasProperty('e2e') })
Loading
Loading