diff --git a/.github/workflows/agent-e2e.yml b/.github/workflows/agent-e2e.yml index ae4c1f509..eb9e716d4 100644 --- a/.github/workflows/agent-e2e.yml +++ b/.github/workflows/agent-e2e.yml @@ -2,12 +2,8 @@ name: Agent E2E # Runs the agent e2e suites (e2e/) against the Conductor OSS server boot # JAR (Maven Central) — the server this SDK ships against, with the agent -# runtime on by default from 3.32.0-rc.8 onward. The Agentspan server JAR -# is no longer used here; the Agentspan CLI binary is still downloaded -# (its own separate pin) since e2e/test_suite16_cli_skills.py drives it -# against this same server. -# tests/integration/ai stays manual-only (same as upstream Agentspan, -# which never ran its tests/integration in CI): run it locally against +# runtime on by default from 3.32.0-rc.8 onward. +# tests/integration/ai stays manual-only: run it locally against # a live server with `pytest tests/integration/ai`. # # These tests call real LLMs via the OPENAI_API_KEY / ANTHROPIC_API_KEY @@ -16,8 +12,6 @@ name: Agent E2E on: [pull_request, workflow_dispatch] -# least privilege: the token is only used to download the public agentspan -# CLI release asset (gh release download) — read access suffices permissions: contents: read @@ -27,7 +21,6 @@ concurrency: env: CONDUCTOR_SERVER_VERSION: "3.32.0-rc.8" # pinned server release — bump deliberately - AGENTSPAN_CLI_VERSION: "0.4.4" # pinned CLI release — independent of the server (revert to 0.4.3 if this breaks) jobs: agent-e2e: @@ -36,8 +29,7 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - AGENTSPAN_SERVER_URL: http://localhost:8080/api - AGENTSPAN_CLI_PATH: ${{ github.workspace }}/agentspan + CONDUCTOR_SERVER_URL: http://localhost:8080/api steps: - name: Checkout code uses: actions/checkout@v4 @@ -67,14 +59,6 @@ jobs: curl -fL --retry 3 -o conductor-server.jar \ "https://repo1.maven.org/maven2/org/conductoross/conductor-server/${CONDUCTOR_SERVER_VERSION}/conductor-server-${CONDUCTOR_SERVER_VERSION}-boot.jar" - - name: Download CLI binary from release - env: - GH_TOKEN: ${{ github.token }} - run: | - gh release download "v${AGENTSPAN_CLI_VERSION}" --repo agentspan-ai/agentspan \ - --pattern "agentspan_linux_amd64" --output agentspan - chmod +x agentspan - - name: Install SDK + test deps run: | python -m pip install --upgrade pip diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 9fe1d4906..15056aeb7 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -32,7 +32,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -e . - pip install pytest pytest-cov coverage + pip install pytest pytest-cov coverage jsonschema - name: Verify agents import without extras installed id: agent_base_import diff --git a/.gitignore b/.gitignore index 0dd6548f0..34da67052 100644 --- a/.gitignore +++ b/.gitignore @@ -173,3 +173,4 @@ tests/unit/automator/_trial_temp/_trial_marker # agent e2e bundle staging output (scripts/package-e2e-bundle.sh) scripts/e2e-bundle-dist/ +/design/openspec diff --git a/README.md b/README.md index 6daa47290..9869f5507 100644 --- a/README.md +++ b/README.md @@ -1,599 +1,186 @@ # Python SDK for Conductor -[![CI](https://github.com/conductor-sdk/conductor-python/actions/workflows/pull_request.yml/badge.svg)](https://github.com/conductor-sdk/conductor-python/actions/workflows/pull_request.yml) +[![CI](https://github.com/conductor-oss/python-sdk/actions/workflows/pull_request.yml/badge.svg)](https://github.com/conductor-oss/python-sdk/actions/workflows/pull_request.yml) [![PyPI](https://img.shields.io/pypi/v/conductor-python.svg)](https://pypi.org/project/conductor-python/) [![Python Versions](https://img.shields.io/pypi/pyversions/conductor-python.svg)](https://pypi.org/project/conductor-python/) [![License](https://img.shields.io/pypi/l/conductor-python.svg)](LICENSE) -Python SDK for [Conductor](https://www.conductor-oss.org/) (OSS and Orkes Conductor) — an orchestration platform for building distributed applications, AI agents, and workflow-driven microservices. Define workflows as code, run workers anywhere, and let Conductor handle retries, state management, and observability. - -If you find [Conductor](https://github.com/conductor-oss/conductor) useful, please consider giving it a star on GitHub — it helps the project grow. - -[![GitHub stars](https://img.shields.io/github/stars/conductor-oss/conductor.svg?style=social&label=Star&maxAge=)](https://GitHub.com/conductor-oss/conductor/) - - -* [Start Conductor Server](#start-conductor-server) -* [Install the SDK](#install-the-sdk) -* [60-Second Quickstart](#60-second-quickstart) -* [Feature Showcase](#feature-showcase) - * [Workers: Sync and Async](#workers-sync-and-async) - * [Workflows with HTTP Calls and Waits](#workflows-with-http-calls-and-waits) - * [Long-Running Tasks with TaskContext](#long-running-tasks-with-taskcontext) - * [Lease Extension for Long-Running Tasks](#lease-extension-for-long-running-tasks) - * [Monitoring with Metrics](#monitoring-with-metrics) - * [Managing Workflow Executions](#managing-workflow-executions) -* [AI & LLM Workflows](#ai--llm-workflows) -* [AI Agents](#ai-agents) -* [Why Conductor?](#why-conductor) -* [Examples](#examples) -* [Documentation](#documentation) -* [Frequently Asked Questions](#frequently-asked-questions) -* [Support](#support) -* [License](#license) - - -## Start Conductor Server - -#### Conductor CLI -```shell -# Installs conductor cli -npm install -g @conductor-oss/conductor-cli - -# Start the open source conductor server on port 8080 -conductor server start -# see conductor server --help for all the available commands -``` +The Python SDK for [Conductor](https://www.conductor-oss.org/) lets you build durable Conductor agents, workflows, and workers. Conductor coordinates retries, state, and observability while your Python code runs wherever you deploy it. +**Get involved:** [⭐ Conductor OSS](https://github.com/conductor-oss/conductor) · [Choose a Conductor OSS contribution](https://github.com/conductor-oss/conductor/contribute) · [Contribution guide](https://github.com/conductor-oss/conductor/blob/main/CONTRIBUTING.md) -Alternatively, if you want to use docker - -**Docker Compose** +**Using an AI coding agent?** Load [Conductor Skills](https://github.com/conductor-oss/conductor-skills) so it can create, run, and operate Conductor workflows and Conductor agents: ```shell -docker run -p 8080:8080 conductoross/conductor:latest +npm install -g @conductor-oss/conductor-skills && conductor-skills --all ``` -The UI will be available at `http://localhost:8080` and the API at `http://localhost:8080/api` -**MacOS / Linux (one-liner):** (If you don't want to use docker, you can install and run the binary directly) -```shell -curl -sSL https://raw.githubusercontent.com/conductor-oss/conductor/main/conductor_server.sh | sh -``` +## Choose your path +| I want to… | Start here | +|---|---| +| Build a durable Conductor agent with tools and human approval | [Run an AI agent example](#ai-agent-quickstart) | +| Bring an existing Google ADK, LangChain, LangGraph, OpenAI Agents, or Claude Agent SDK agent | [Use framework bridges](#framework-bridges) | +| Build a durable workflow and Python worker | [Run the core hello-world example](#workflow-and-worker-quickstart) | +| Browse all examples | [AI agent guide](docs/agents/README.md) · [Core examples](docs/examples.md) | +| Navigate the SDK documentation | [Documentation hub](docs/README.md) | -## Install the SDK +## Choose your Conductor server -```shell -python3 -m venv conductor-env -source conductor-env/bin/activate # Windows: conductor-env\Scripts\activate -pip install conductor-python -``` +Connect to a server before following either quickstart. Use the hosted Developer Edition by default, or run Conductor locally when you need a self-managed development environment. -> **Already in a virtual environment?** Skip the `venv` step and run `pip install conductor-python` directly. On macOS, Windows, or in containers where system Python is not locked down, you can also install globally. +### Recommended: Orkes Developer Edition -Building durable AI agents instead? Install the `agents` extra (or a per-framework extra — -see [AI Agents](#ai-agents)): +[Orkes Developer Edition](https://developer.orkescloud.com/) is the default hosted option. Create an application and access key in the Developer Edition UI, then configure this SDK with its API endpoint. Keep the key and secret out of source control. ```shell -pip install 'conductor-python[agents]' +export CONDUCTOR_SERVER_URL=https://developer.orkescloud.com/api +export CONDUCTOR_AUTH_KEY= +export CONDUCTOR_AUTH_SECRET= ``` -## 60-Second Quickstart - -**Step 1: Create a workflow** - -Workflows are definitions that reference task types (e.g. a SIMPLE task called `greet`). We'll build a workflow called -`greetings` that runs one task and returns its output. - -Assuming you have a `WorkflowExecutor` (`executor`) and a worker task (`greet`): - -```python -from conductor.client.workflow.conductor_workflow import ConductorWorkflow - -workflow = ConductorWorkflow(name='greetings', version=1, executor=executor) -greet_task = greet(task_ref_name='greet_ref', name=workflow.input('name')) -workflow >> greet_task -workflow.output_parameters({'result': greet_task.output('result')}) -workflow.register(overwrite=True) -``` - -**Step 2: Write a worker** - -Workers are just Python functions decorated with `@worker_task` that poll Conductor for tasks and execute them. - -```python -from conductor.client.worker.worker_task import worker_task - -# register_task_def=True is convenient for local dev quickstarts; in production, manage task definitions separately. -@worker_task(task_definition_name='greet', register_task_def=True) -def greet(name: str) -> str: - return f'Hello {name}' -``` - -**Step 3: Run your first workflow app** - -Create a `quickstart.py` with the following: - -```python -from conductor.client.automator.task_handler import TaskHandler -from conductor.client.configuration.configuration import Configuration -from conductor.client.orkes_clients import ConductorClients # OrkesClients is an alias for the same class -from conductor.client.workflow.conductor_workflow import ConductorWorkflow -from conductor.client.worker.worker_task import worker_task - - -# A worker is any Python function. -@worker_task(task_definition_name='greet', register_task_def=True) -def greet(name: str) -> str: - return f'Hello {name}' - - -def main(): - # Configure the SDK (reads CONDUCTOR_SERVER_URL / CONDUCTOR_AUTH_* from env). - config = Configuration() - - clients = ConductorClients(configuration=config) - executor = clients.get_workflow_executor() +For another hosted or self-managed remote cluster, use that cluster's `/api` URL and its application credentials instead. See [server setup](docs/server-setup.md) for details. - # Build a workflow with the >> operator. - workflow = ConductorWorkflow(name='greetings', version=1, executor=executor) - greet_task = greet(task_ref_name='greet_ref', name=workflow.input('name')) - workflow >> greet_task - workflow.output_parameters({'result': greet_task.output('result')}) - workflow.register(overwrite=True) +### Local alternative: Conductor CLI - # Start polling for tasks (one worker subprocess per worker function). - # Note: scan_for_annotated_workers=True only discovers @worker_task functions that have - # already been imported. If workers are in a separate module, import it first. - with TaskHandler(configuration=config, scan_for_annotated_workers=True) as task_handler: - task_handler.start_processes() - - # Run the workflow and get the result. - run = executor.execute(name='greetings', version=1, workflow_input={'name': 'Conductor'}) - print(f'result: {run.output["result"]}') - print(f'execution: {config.ui_host}/execution/{run.workflow_id}') - - -if __name__ == '__main__': - main() -``` - -Run it: +The CLI is the preferred local-server path. Install the CLI, start the server, then point the SDK at its API endpoint. ```shell -python quickstart.py -``` - -> **About `OrkesClients`:** This is the standard client factory for Conductor. The `Orkes` prefix reflects the implementing organization — it works identically with self-hosted OSS Conductor. No Orkes account or paid service is required. - -> ### Using Orkes Conductor / Remote Server? -> Export your authentication credentials as well: -> -> ```shell -> export CONDUCTOR_SERVER_URL="https://your-cluster.orkesconductor.io/api" -> -> # If using Orkes Conductor that requires auth key/secret -> export CONDUCTOR_AUTH_KEY="your-key" -> export CONDUCTOR_AUTH_SECRET="your-secret" -> -> # Optional — set to false to force HTTP/1.1 if your network environment has unstable long-lived HTTP/2 connections (default: true) -> # export CONDUCTOR_HTTP2_ENABLED=false -> ``` -> See [Configuration](#configuration) for details. - -That's it — you just defined a worker, built a workflow, and executed it. Open the Conductor UI (default: -[http://localhost:8080](http://localhost:8080)) to see the execution. - ---- - -## Feature Showcase - -### Workers: Sync and Async - -The SDK automatically selects the right runner based on your function signature — `TaskRunner` (thread pool) for sync functions, `AsyncTaskRunner` (event loop) for async. - -```python -from conductor.client.worker.worker_task import worker_task - -# Sync worker — for CPU-bound work (uses ThreadPoolExecutor) -@worker_task(task_definition_name='process_image', thread_count=4) -def process_image(image_url: str) -> dict: - import PIL.Image, io, requests - img = PIL.Image.open(io.BytesIO(requests.get(image_url).content)) - img.thumbnail((256, 256)) - return {'width': img.width, 'height': img.height} - - -# Async worker — for I/O-bound work (uses AsyncTaskRunner, no thread overhead) -@worker_task(task_definition_name='fetch_data', thread_count=50) -async def fetch_data(url: str) -> dict: - import httpx - async with httpx.AsyncClient() as client: - resp = await client.get(url) - return resp.json() -``` - -Start workers with `TaskHandler` — it auto-discovers `@worker_task` functions and spawns one subprocess per worker: - -```python -from conductor.client.automator.task_handler import TaskHandler -from conductor.client.configuration.configuration import Configuration - -config = Configuration() -with TaskHandler(configuration=config, scan_for_annotated_workers=True) as task_handler: - task_handler.start_processes() - task_handler.join_processes() # blocks forever (workers poll continuously) -``` - -See [examples/worker_example.py](examples/worker_example.py) and [examples/workers_e2e.py](examples/workers_e2e.py) for complete examples. - -### Workflows with HTTP Calls and Waits - -Chain custom workers with built-in system tasks — HTTP calls, waits, JavaScript, JQ transforms — all in one workflow: - -```python -from conductor.client.workflow.conductor_workflow import ConductorWorkflow -from conductor.client.workflow.task.http_task import HttpTask -from conductor.client.workflow.task.wait_task import WaitTask - -workflow = ConductorWorkflow(name='order_pipeline', version=1, executor=executor) - -# Custom worker task -validate = validate_order(task_ref_name='validate', order_id=workflow.input('order_id')) - -# Built-in HTTP task — call any API, no worker needed -charge_payment = HttpTask(task_ref_name='charge_payment', http_input={ - 'uri': 'https://api.stripe.com/v1/charges', - 'method': 'POST', - 'headers': {'Authorization': ['Bearer ${workflow.input.stripe_key}']}, - 'body': {'amount': '${validate.output.amount}'} -}) - -# Built-in Wait task — pause the workflow for 10 seconds -cool_down = WaitTask(task_ref_name='cool_down', wait_for_seconds=10) - -# Another custom worker task -notify = send_notification(task_ref_name='notify', message='Order complete') - -# Chain with >> operator -workflow >> validate >> charge_payment >> cool_down >> notify - -# Execute synchronously and wait for the result -result = workflow.execute(workflow_input={'order_id': 'ORD-123', 'stripe_key': 'sk_test_...'}) -print(result.output) -``` - -See [examples/kitchensink.py](examples/kitchensink.py) for all task types (HTTP, JavaScript, JQ, Switch, Terminate) and [examples/workflow_ops.py](examples/workflow_ops.py) for lifecycle operations. - -### Long-Running Tasks with TaskContext - -For tasks that take minutes or hours (batch processing, ML training, external approvals), use `TaskContext` to report progress and poll incrementally: - -```python -from typing import Union -from conductor.client.worker.worker_task import worker_task -from conductor.client.context.task_context import get_task_context, TaskInProgress - -@worker_task(task_definition_name='batch_job') -def batch_job(batch_id: str) -> Union[dict, TaskInProgress]: - ctx = get_task_context() - ctx.add_log(f"Processing batch {batch_id}, poll #{ctx.get_poll_count()}") - - if ctx.get_poll_count() < 3: - # Not done yet — re-queue and check again in 30 seconds - return TaskInProgress(callback_after_seconds=30, output={'progress': ctx.get_poll_count() * 33}) - - # Done after 3 polls - return {'status': 'completed', 'batch_id': batch_id} -``` - -`TaskContext` also provides access to task metadata, retry counts, workflow IDs, and the ability to add logs visible in the Conductor UI. - -See [examples/task_context_example.py](examples/task_context_example.py) for all patterns (polling, retry-aware logic, async context, input access). - -### Lease Extension for Long-Running Tasks - -For tasks that run longer than `responseTimeoutSeconds` (e.g., LLM inference, data pipelines, batch jobs), enable automatic lease extension. The SDK sends heartbeats at 80% of `responseTimeoutSeconds`, resetting the server's timeout timer so the task stays alive: - -```python -from conductor.client.worker.worker_task import worker_task - -@worker_task( - task_definition_name='train_model', - lease_extend_enabled=True, # Automatic heartbeat — keeps task alive -) -def train_model(dataset_id: str) -> dict: - """Runs for 10 minutes, but responseTimeoutSeconds is only 60s. - Heartbeats at 48s intervals keep the lease alive.""" - model = train(dataset_id) - return {'model_id': model.id, 'accuracy': model.accuracy} +npm install -g @conductor-oss/conductor-cli +conductor server start +conductor server status +export CONDUCTOR_SERVER_URL=http://localhost:8080/api ``` -Disabled by default. Enable per-worker via decorator, constructor, or environment variable (`conductor_worker__lease_extend_enabled=true`). See [LEASE_EXTENSION.md](docs/LEASE_EXTENSION.md) for the full guide. - -### Monitoring with Metrics - -Enable Prometheus metrics with a single setting — the SDK exposes poll counts, execution times, error rates, and HTTP latency: - -```python -from conductor.client.automator.task_handler import TaskHandler -from conductor.client.configuration.configuration import Configuration -from conductor.client.configuration.settings.metrics_settings import MetricsSettings +### Docker fallback -config = Configuration() -metrics = MetricsSettings(directory='/tmp/conductor-metrics', http_port=8000) - -with TaskHandler(configuration=config, metrics_settings=metrics, scan_for_annotated_workers=True) as task_handler: - task_handler.start_processes() - task_handler.join_processes() -``` +Use Docker when you need a containerized local server instead of the CLI: ```shell -# Prometheus-compatible endpoint -curl http://localhost:8000/metrics +docker run --rm -p 8080:8080 -p 1234:5000 conductoross/conductor:latest +export CONDUCTOR_SERVER_URL=http://localhost:8080/api ``` -Legacy metrics are emitted by default. Set `WORKER_CANONICAL_METRICS=true` before starting workers to use the canonical metric catalog. See [examples/metrics_example.py](examples/metrics_example.py) and [METRICS.md](METRICS.md) for the full legacy and canonical reference. - -### Managing Workflow Executions - -Full lifecycle control — start, execute, pause, resume, terminate, retry, restart, rerun, signal, and search: - -```python -from conductor.client.configuration.configuration import Configuration -from conductor.client.http.models import StartWorkflowRequest, RerunWorkflowRequest, TaskResult -from conductor.client.orkes_clients import ConductorClients - -config = Configuration() -clients = ConductorClients(configuration=config) -workflow_client = clients.get_workflow_client() -task_client = clients.get_task_client() -executor = clients.get_workflow_executor() - -# Start async (returns workflow ID immediately) -workflow_id = executor.start_workflow(StartWorkflowRequest(name='my_workflow', input={'key': 'value'})) - -# Execute sync (blocks until workflow completes) -result = executor.execute(name='my_workflow', version=1, workflow_input={'key': 'value'}) - -# Lifecycle management -workflow_client.pause_workflow(workflow_id) -workflow_client.resume_workflow(workflow_id) -workflow_client.terminate_workflow(workflow_id, reason='no longer needed') -workflow_client.retry_workflow(workflow_id) # retry from last failed task -workflow_client.restart_workflow(workflow_id) # restart from the beginning -workflow_client.rerun_workflow(workflow_id, # rerun from a specific task - RerunWorkflowRequest(re_run_from_task_id=task_id)) +The Docker server UI is available at [http://localhost:1234](http://localhost:1234). See [server setup](docs/server-setup.md) for full local, remote, and authentication guidance. -# Send a signal to a waiting workflow (complete a WAIT task externally) -task_client.update_task(TaskResult( - workflow_instance_id=workflow_id, - task_id=wait_task_id, - status='COMPLETED', - output_data={'approved': True} -)) - -# Search workflows -results = workflow_client.search(query='status IN (RUNNING) AND correlationId = "order-123"') -``` - -See [examples/workflow_ops.py](examples/workflow_ops.py) for a complete walkthrough of every operation. - ---- - -## AI & LLM Workflows +## Why Conductor? -Conductor supports AI-native workflows including agentic tool calling, RAG pipelines, and multi-agent orchestration. +- **Survive process failures:** execution state is durable, so Conductor agents and workflows resume from completed work. +- **Build dynamic agent graphs:** define workflow graphs in Python or let an LLM plan them at runtime. Conductor executes plans as durable sub-workflows rather than transient in-process loops. +- **Run tools as distributed tasks:** scale Python workers independently while Conductor manages retries and delivery. +- **Orchestrate long-running work:** combine AI, schedules, events, and human approval without holding application threads open. +- **See every execution:** inspect inputs, outputs, tool calls, retries, and status through one execution model. -**Agentic Workflows** +See the maintained [planner-context example](examples/agents/115_plan_execute_planner_context.py) for a durable plan-and-execute graph, or start with the [agent examples](examples/agents/README.md). -Build AI agents where LLMs dynamically select and call Python workers as tools. See [examples/agentic_workflows/](examples/agentic_workflows/) for all examples. +## Requirements and compatibility -| Example | Description | -|---------|-------------| -| [llm_chat.py](examples/agentic_workflows/llm_chat.py) | Automated multi-turn science Q&A between two LLMs | -| [llm_chat_human_in_loop.py](examples/agentic_workflows/llm_chat_human_in_loop.py) | Interactive chat with WAIT task pauses for user input | -| [multiagent_chat.py](examples/agentic_workflows/multiagent_chat.py) | Multi-agent debate with moderator routing between panelists | -| [function_calling_example.py](examples/agentic_workflows/function_calling_example.py) | LLM picks which Python function to call based on user queries | -| [mcp_weather_agent.py](examples/agentic_workflows/mcp_weather_agent.py) | AI agent using MCP tools for weather queries | +- Python 3.10+ +- A running OSS or Orkes Conductor server selected in [Choose your Conductor server](#choose-your-conductor-server) +- Docker when using the Docker local-server option +- Node.js/npm only when using the optional Conductor CLI -**LLM and RAG Workflows** +The CI workflows are the source of truth for the server versions exercised by this SDK. See the [agent E2E matrix](.github/workflows/agent-e2e.yml) for its pinned server version. -| Example | Description | -|---------|-------------| -| [rag_workflow.py](examples/rag_workflow.py) | End-to-end RAG: document conversion (PDF/Word/Excel), pgvector indexing, semantic search, answer generation | -| [vector_db_helloworld.py](examples/orkes/vector_db_helloworld.py) | Vector database operations: text indexing, embedding generation, and semantic search with Pinecone | +## Install the SDK ```shell -# Automated multi-turn chat -python examples/agentic_workflows/llm_chat.py - -# Multi-agent debate -python examples/agentic_workflows/multiagent_chat.py --topic "renewable energy" - -# RAG pipeline -pip install "markitdown[pdf]" -python examples/rag_workflow.py document.pdf "What are the key findings?" +python3 -m venv conductor-env +source conductor-env/bin/activate # Windows: conductor-env\Scripts\activate +pip install conductor-python ``` ---- - -## AI Agents +### AI agents -Beyond the workflow-embedded LLM calls above, `conductor-python` also ships a durable -agent-authoring layer — `Agent`, `AgentRuntime`, `tool`, guardrails, handoffs, and multi-agent -strategies — where the agent itself compiles into a Conductor workflow that runs on the server, -with retries, durable state, streaming, and human-in-the-loop pauses built in. +Install the complete Conductor agent surface, including supported framework bridges: ```shell pip install 'conductor-python[agents]' ``` -```python -from conductor.ai.agents import Agent, AgentRuntime +### Workflows and workers -agent = Agent(name="greeter", model="anthropic/claude-sonnet-4-6", - instructions="You are a friendly assistant.") +The base package includes the workflow, task, worker, metadata, scheduler, and metrics clients: -with AgentRuntime() as runtime: - result = runtime.run(agent, "Say hello.") - print(result.output) +```shell +pip install conductor-python ``` -Framework integrations (LangChain, LangGraph, Google ADK, the OpenAI Agents SDK, Claude Agent SDK) -are optional extras — see [docs/agents/getting-started.md](docs/agents/getting-started.md) for -per-framework install instructions, and [examples/agents/](examples/agents/) for 270+ runnable -examples. Full docs: [docs/agents/](docs/agents/). +### Modules ---- - -## Why Conductor? - -| | | +| Package or extra | Use it for | |---|---| -| **Language agnostic** | Workers in Python, Java, Go, JS, C# — all in one workflow | -| **Durable execution** | Survives crashes, retries automatically, never loses state | -| **Built-in HTTP/Wait/JS tasks** | No code needed for common operations | -| **Horizontal scaling** | Built at Netflix for millions of workflows | -| **Full visibility** | UI shows every execution, every task, every retry | -| **Sync + Async execution** | Start-and-forget OR wait-for-result | -| **Human-in-the-loop** | WAIT tasks pause until an external signal | -| **AI-native** | LLM chat, RAG pipelines, function calling, MCP tools built-in | - ---- - -## Examples - -See the [Examples Guide](examples/README.md) for the full catalog. Key examples: - -| Example | Description | Run | -|---------|-------------|-----| -| [workers_e2e.py](examples/workers_e2e.py) | End-to-end: sync + async workers, metrics | `python examples/workers_e2e.py` | -| [kitchensink.py](examples/kitchensink.py) | All task types (HTTP, JS, JQ, Switch) | `python examples/kitchensink.py` | -| [workflow_ops.py](examples/workflow_ops.py) | Pause, resume, terminate, retry, restart, rerun, signal | `python examples/workflow_ops.py` | -| [task_context_example.py](examples/task_context_example.py) | Long-running tasks with TaskInProgress | `python examples/task_context_example.py` | -| [lease_extension_example.py](examples/lease_extension_example.py) | Automatic heartbeat for long-running tasks | `python examples/lease_extension_example.py` | -| [metrics_example.py](examples/metrics_example.py) | Prometheus metrics collection | `python examples/metrics_example.py` | -| [fastapi_worker_service.py](examples/fastapi_worker_service.py) | FastAPI: expose a workflow as an API (+ workers) | `uvicorn examples.fastapi_worker_service:app --port 8081 --workers 1` | -| [helloworld.py](examples/helloworld/helloworld.py) | Minimal hello world | `python examples/helloworld/helloworld.py` | -| [dynamic_workflow.py](examples/dynamic_workflow.py) | Build workflows programmatically | `python examples/dynamic_workflow.py` | -| [test_workflows.py](examples/test_workflows.py) | Unit testing workflows | `python -m unittest examples.test_workflows` | - -**API Journey Examples** - -End-to-end examples covering all APIs for each domain: - -| Example | APIs | Run | -|---------|------|-----| -| [authorization_journey.py](examples/authorization_journey.py) | Authorization APIs | `python examples/authorization_journey.py` | -| [metadata_journey.py](examples/metadata_journey.py) | Metadata APIs | `python examples/metadata_journey.py` | -| [schedule_journey.py](examples/schedule_journey.py) | Schedule APIs | `python examples/schedule_journey.py` | -| [prompt_journey.py](examples/prompt_journey.py) | Prompt APIs | `python examples/prompt_journey.py` | - -## Documentation - -| Document | Description | -|----------|-------------| -| [Worker Design](docs/design/WORKER_DESIGN.md) | Architecture: AsyncTaskRunner vs TaskRunner, discovery, lifecycle | -| [Worker Guide](docs/WORKER.md) | All worker patterns (function, class, annotation, async) | -| [Worker Configuration](WORKER_CONFIGURATION.md) | Hierarchical environment variable configuration | -| [Lease Extension](docs/LEASE_EXTENSION.md) | Automatic heartbeat for long-running tasks | -| [Workflow Management](docs/WORKFLOW.md) | Start, pause, resume, terminate, retry, search | -| [Workflow Testing](docs/WORKFLOW_TESTING.md) | Unit testing with mock outputs | -| [Task Management](docs/TASK_MANAGEMENT.md) | Task operations | -| [Metadata](docs/METADATA.md) | Task & workflow definitions | -| [Authorization](docs/AUTHORIZATION.md) | Users, groups, applications, permissions | -| [Schedules](docs/SCHEDULE.md) | Workflow scheduling | -| [Secrets](docs/SECRET_MANAGEMENT.md) | Secret storage | -| [Prompts](docs/PROMPT.md) | AI/LLM prompt templates | -| [Integrations](docs/INTEGRATION.md) | AI/LLM provider integrations | -| [AI Agents](docs/agents/README.md) | Durable agent authoring: `Agent`, `AgentRuntime`, tools, guardrails, handoffs | -| [Metrics](METRICS.md) | Prometheus metrics collection | -| [Examples](examples/README.md) | Complete examples catalog | - -## Frequently Asked Questions - -**Is this the same as Netflix Conductor?** - -Yes. Conductor OSS is the continuation of the original [Netflix Conductor](https://github.com/Netflix/conductor) repository after Netflix contributed the project to the open-source foundation. - -**Is this project actively maintained?** - -Yes. [Orkes](https://orkes.io) is the primary maintainer and offers an enterprise SaaS platform for Conductor across all major cloud providers. - -**Can Conductor scale to handle my workload?** - -Conductor was built at Netflix to handle massive scale and has been battle-tested in production environments processing millions of workflows. It scales horizontally to meet virtually any demand. +| `conductor-python` | Workflow, task, worker, metadata, scheduler, and metrics clients | +| `conductor-python[agents]` | Durable Conductor agents, tools, guardrails, handoffs, and all supported framework bridges | +| `conductor-python[adk]` | Google ADK bridge | +| `conductor-python[langchain]` | LangChain bridge | +| `conductor-python[langgraph]` | LangGraph bridge | +| `conductor-python[openai-agents]` | OpenAI Agents bridge | +| `conductor-python[claude]` | Claude Agent SDK bridge | -**Does Conductor support durable code execution?** +## AI agent quickstart -Yes. Conductor ensures workflows complete reliably even in the face of infrastructure failures, process crashes, or network issues. +Use this path when your Conductor agent needs LLM reasoning, tools, guardrails, handoffs, or human approval. Select a server above first. For a local server, configure the LLM provider credential in the server environment before starting it. For Developer Edition, configure the provider integration in the hosted cluster. The [agent getting-started guide](docs/agents/getting-started.md) covers both paths. -**Are workflows always asynchronous?** - -No. While Conductor excels at asynchronous orchestration, it also supports synchronous workflow execution when immediate results are required. - -**Why did `execute()` return `status: RUNNING` with no output?** - -`execute()` blocks until the workflow finishes **or** `wait_for_seconds` elapses (default: 10 s), -whichever comes first. If it times out, you get `status='RUNNING'` — that is correct behavior, -not a bug. - -The most common cause: your worker raised an exception. Conductor marks the task FAILED and -schedules a retry after `retryDelaySeconds` (default: **60 s**). The default 10 s wait expires -while the retry is pending, so `execute()` returns before the workflow completes. - -**To fix**: increase `wait_for_seconds` to outlast the retry cycle: - -```python -# default retryDelaySeconds is 60 — wait long enough to cover one retry -run = executor.execute(name='my_workflow', version=1, workflow_input={...}, wait_for_seconds=70) -``` - -**To debug** when a workflow is stuck: - -```python -# Inspect task statuses and failure reasons -wf = executor.get_workflow(run.workflow_id, include_tasks=True) -for task in wf.tasks: - if task.status in ('FAILED', 'FAILED_WITH_TERMINAL_ERROR'): - print(task.reference_task_name, task.reason_for_incompletion) +```shell +export CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini +cd examples/agents +python 01_basic_agent.py ``` -You can also open the Conductor UI at `/execution/` — it shows each task's -status, retry count, and the worker exception message directly. Worker tracebacks are also logged -at ERROR level by the SDK in the `TaskHandler` process. - -**Do I need to use a Conductor-specific framework?** - -No. Conductor is language and framework agnostic. Use your preferred language and framework — the [SDKs](https://github.com/conductor-oss/conductor#conductor-sdks) provide native integration for Python, Java, JavaScript, Go, C#, and more. - -**Can I mix workers written in different languages?** +Expected outcome: the example prints an `AgentResult` containing the model response. Continue with the [AI agent guide](docs/agents/README.md), [tools guide](docs/agents/concepts/tools.md), and [agent examples](examples/agents/README.md). -Yes. A single workflow can have workers written in Python, Java, Go, or any other supported language. Workers communicate through the Conductor server, not directly with each other. +### Framework bridges -**What Python versions are supported?** +Keep using the Python agent framework your team already knows. The SDK bridges native [Google ADK](docs/agents/frameworks/google-adk.md), [LangChain](docs/agents/frameworks/langchain.md), [LangGraph](docs/agents/frameworks/langgraph.md), [OpenAI Agents](docs/agents/frameworks/openai.md), and [Claude Agent SDK](docs/agents/frameworks/claude-agent-sdk.md) agents into durable Conductor agents. -Python 3.9 and above. +## Workflow and worker quickstart -**Should I use `def` or `async def` for my workers?** +With a server selected above, this maintained example registers a workflow, starts a Python worker, executes the workflow, and prints its result. -Use `async def` for I/O-bound tasks (API calls, database queries) — the SDK uses `AsyncTaskRunner` with a single event loop for high concurrency with low overhead. Use regular `def` for CPU-bound or blocking work — the SDK uses `TaskRunner` with a thread pool. The SDK selects the right runner automatically based on your function signature. +```shell +cd examples/helloworld +python helloworld.py +``` -**How do I run workers in production?** +Expected outcome: the workflow finishes with `COMPLETED` and prints its greeting output. For worker patterns, workflow definitions, and testing, continue with the [core examples catalog](docs/examples.md), [worker guide](docs/workers.md), and [workflow guide](docs/workflows.md). -Workers are standard Python processes. Deploy them as you would any Python application — in containers, VMs, or bare metal. Workers poll the Conductor server for tasks, so no inbound ports need to be opened. See [Worker Design](docs/design/WORKER_DESIGN.md) for architecture details. +## Common tasks -**How do I test workflows without running a full Conductor server?** +| Need | Start with | +|---|---| +| Build Python Conductor agents | [Agent concepts](docs/agents/concepts/agents.md) | +| Add tools and human approval | [Agent tools](docs/agents/concepts/tools.md) | +| Use another agent framework | [Google ADK](docs/agents/frameworks/google-adk.md) · [LangChain](docs/agents/frameworks/langchain.md) · [LangGraph](docs/agents/frameworks/langgraph.md) · [OpenAI Agents](docs/agents/frameworks/openai.md) | +| Deploy, serve, and run Conductor agents | [Agent runtime modes](docs/agents/concepts/deploy-serve-run.md) | +| Implement and scale Python workers | [Workers guide](docs/workers.md) · [reliability](docs/reliability.md) | +| Define and evolve workflows | [Workflows guide](docs/workflows.md) · [lifecycle/versioning](docs/workflow-lifecycle.md) | +| Upload/download workflow-scoped files | [Python compatibility](docs/compatibility.md#workflow-scoped-files) | +| Test workflows and workers | [Workflow testing](docs/workflow-testing.md) | +| Expose worker metrics | [Observability](docs/observability.md) | +| Host Python workers in an application | [FastAPI worker example](examples/fastapi_worker_service.py) · [deployment/scaling](docs/deployment-scaling.md) | +| Manage schedules and events | [Schedules/events guide](docs/schedules-events.md) | +| Find typed clients and API references | [Core API map](docs/api-map.md) | + +## Troubleshooting + +| Symptom | Check | +|---|---| +| Connection refused | The server is healthy at `http://localhost:8080/health`; `CONDUCTOR_SERVER_URL` ends in `/api`. | +| Task remains `SCHEDULED` | A worker is polling the exact task type and has enough worker capacity. | +| Authentication failure | `CONDUCTOR_AUTH_KEY` and `CONDUCTOR_AUTH_SECRET` are set for the target server. | +| Conductor agent cannot call a model | The server—not only the Python process—has a configured LLM provider and model. | -The SDK provides a test framework that uses Conductor's `POST /api/workflow/test` endpoint to evaluate workflows with mock task outputs. See [Workflow Testing](docs/WORKFLOW_TESTING.md) for details. +## Support and project policies -## Support +**Contribute upstream:** [Choose a Conductor OSS contribution](https://github.com/conductor-oss/conductor/contribute) · [Read the Conductor OSS contribution guide](https://github.com/conductor-oss/conductor/blob/main/CONTRIBUTING.md) -- [Open an issue (SDK)](https://github.com/conductor-sdk/conductor-python/issues) for SDK bugs, questions, and feature requests -- [Open an issue (Conductor server)](https://github.com/conductor-oss/conductor/issues) for Conductor OSS server issues -- [Join the Conductor Slack](https://join.slack.com/t/orkes-conductor/shared_invite/zt-2vdbx239s-Eacdyqya9giNLHfrCavfaA) for community discussion and help -- [Orkes Community Forum](https://community.orkes.io/) for Q&A +- [SDK issues](https://github.com/conductor-oss/python-sdk/issues) for Python SDK bugs and feature requests +- [Conductor server issues](https://github.com/conductor-oss/conductor/issues) for OSS server behavior +- [Conductor Code of Conduct](https://github.com/conductor-oss/conductor/blob/main/CODE_OF_CONDUCT.md) for community expectations and conduct reporting +- [Conductor security policy](https://github.com/conductor-oss/conductor/security/policy) for private vulnerability reporting +- [Conductor Slack](https://join.slack.com/t/orkes-conductor/shared_invite/zt-2vdbx239s-Eacdyqya9giNLHfrCavfaA) and the [Orkes Community Forum](https://community.orkes.io/) for questions ## License -Apache 2.0 +Apache 2.0. See [LICENSE](LICENSE). diff --git a/SDK_UPDATE_v1.md b/SDK_UPDATE_v1.md deleted file mode 100644 index 99f8afd4f..000000000 --- a/SDK_UPDATE_v1.md +++ /dev/null @@ -1,326 +0,0 @@ -# SDK Update v1 — Agent Layer: Components, Method Contracts, and Bug Fixes - -**Source:** Python SDK branch `feat/agent-client-worker-credentials` (PR conductor-oss/python-sdk#421) -**Audience:** Developers and coding agents updating the **Java / Go / C# / JavaScript-TypeScript** Conductor SDKs. -**Purpose:** A single reference describing (1) how the agent-layer components are designed and used — `ApiClient`, `AgentClient`, `AgentRuntime`, `AgentConfig`, `RunSettings` — (2) the exact contracts of `run` / `start` / `deploy` / `serve`, and (3) **every bug fix** on this branch, with root cause and port guidance. - -> Companion document: `docs/design/AGENT_SDK_PORTING_SPEC.md` — the same changes expressed as numbered requirements (R1–R13) with acceptance criteria and a test matrix. Use *this* document to understand the design and the fixes; use the spec to drive implementation. - -Code snippets below are the Python reference implementation; port the contracts, not the syntax. - ---- - -## Part 1 — Components - -### 1.1 `ApiClient` — the single token authority - -The `ApiClient` (and its async twin) is the **only** component in the SDK that performs authentication. Everything else — generated resource APIs, domain clients, the agent control plane, and worker-side HTTP — reuses it. - -**What it owns:** - -| Capability | Behavior | -|---|---| -| Token mint | `POST {host}/token` with `{"keyId": …, "keySecret": …}` → `{"token": ""}` | -| Token cache | Token + mint timestamp held on the `Configuration`; shared by every client built from it | -| TTL refresh | Before injecting the header, if the token is older than the TTL, re-mint proactively | -| 401 retry | Any `call_api` that fails with an auth error force-refreshes the token and retries exactly once | -| Header | `X-Authorization: ` on every request (except `/token` itself); no header for anonymous servers | -| Open-server detection | A 404 from `/token` = auth-disabled server → operate anonymously | - -**Generic call surface** — arbitrary endpoints (used by the agent layer for `/agent/*`): - -```python -api_client.call_api( - "/agent/start", "POST", # resource path is relative to Configuration.host, - {}, [], headers, # which already ends in /api - body=payload, - response_type="object", # None => fire-and-forget (executes, returns None) - auth_settings=["api_key"], - _return_http_data_only=True, -) -``` - -**New in this branch — public auth-header accessor** (for transports that cannot go through `call_api`, e.g. SSE): - -```python -api_client.get_authentication_headers() -# -> {"header": {"X-Authorization": ""}} or None (anonymous) -# TTL-aware: renews the token if expired before returning. -``` - -**Rule for all SDKs:** never mint or cache a token anywhere else. If a component needs auth, give it an `ApiClient` (or this accessor). See bug fixes #3 and #12. - ---- - -### 1.2 `AgentClient` — the agent control plane - -A new **domain client** following the SDK's established interface + Orkes-implementation pattern (like `WorkflowClient` / `OrkesWorkflowClient`). - -``` -AgentClient (interface) ←implements— OrkesAgentClient (built on OrkesBaseClient + ApiClient) - └── created via OrkesClients.get_agent_client() -``` - -**Methods** (each has a sync and an `*_async` variant): - -| Method | HTTP | Notes | -|---|---|---| -| `start_agent(payload)` | `POST /agent/start` | Server compiles + registers + starts in one call; returns `{executionId, requiredWorkers?}` | -| `deploy_agent(payload)` | `POST /agent/deploy` | Register only; returns `{agentName}` | -| `compile_agent(payload)` | `POST /agent/compile` | Returns the compiled workflow definition | -| `get_status(execution_id)` | `GET /agent/{id}/status` | | -| `get_execution(execution_id)` | `GET /agent/execution/{id}` | | -| `list_executions(params?)` | `GET /agent/executions` | Query params | -| `respond(execution_id, body)` | `POST /agent/{id}/respond` | Human-in-the-loop answer | -| `stop(execution_id)` | `POST /agent/{id}/stop` | Graceful stop | -| `signal(execution_id, message)` | `POST /agent/{id}/signal` | Inject persistent context | -| `stream_sse(execution_id, last_event_id?)` | `GET /agent/stream/{id}` | SSE event generator | -| `close()` | — | Release async transports | - -**Implementation rules:** -- All non-streaming methods delegate to `api_client.call_api(...)` → token management inherited, zero token code in this class. -- **SSE** uses a streaming HTTP transport but sets its auth header from `api_client.get_authentication_headers()` on every (re)connect. Sends `Accept: text/event-stream` and `Last-Event-ID` on reconnect. Raises `SSEUnavailableError` when the server has no streaming (callers fall back to status polling). -- **Error mapping:** transport `ApiException` → `AgentNotFoundError` (404) / `AgentAPIError` (everything else), both carrying status, body, url. - -**Usage:** - -```python -clients = OrkesClients(configuration=Configuration()) -agent_client = clients.get_agent_client() -data = agent_client.start_agent({"agentConfig": cfg, "prompt": "hi", "sessionId": "", "media": []}) -``` - ---- - -### 1.3 `AgentRuntime` — the facade - -The single user-facing entry point for authoring-and-running agents. - -**Constructor contract:** - -```python -AgentRuntime(configuration: Optional[Configuration] = None, *, settings: Optional[AgentConfig] = None) -``` - -- `configuration` — the standard SDK `Configuration`; **sole source** of host, auth, and log level. Defaults to env-resolved `Configuration()`. -- `settings` — optional `AgentConfig` (behaviour knobs only, §1.4). Defaults to `AgentConfig.from_env()`. - -**Composition** (all built from ONE `OrkesClients(configuration)`, hence one token cache): - -``` -AgentRuntime - ├── _agent_client = clients.get_agent_client() # control plane (§1.2); exposed as runtime.client - ├── _workflow_client / _task_client # status polling, HITL updates, messaging - └── _worker_manager = WorkerManager(configuration, …) # wraps TaskHandler/TaskRunner; serves tool workers -``` - -- Context manager, sync and async (`with` / `async with`). -- Every `/agent/*` operation goes through `_agent_client`. The runtime holds **no** URL builders, HTTP transports, or token helpers of its own. -- There is **no server auto-start**: the runtime never probes for or launches a server (removed on this branch; a missing server surfaces as a connection error). - -**Execution model:** an agent is authored locally but **compiled and executed on the server** as a Conductor workflow. The agent's tools are ordinary Conductor **workers** served by the runtime's `WorkerManager` — the standard poll/execute/update loop, unchanged. - ---- - -### 1.4 `AgentConfig` — agent-runtime behaviour ONLY - -Reduced on this branch to behaviour knobs. **Connection, auth, and log level are NOT here** — they live on `Configuration` (see bug fix #8). - -| Field | Type | Default | Env var | -|---|---|---|---| -| `worker_poll_interval_ms` | int | 100 | `AGENTSPAN_WORKER_POLL_INTERVAL` | -| `worker_thread_count` | int | 1 | `AGENTSPAN_WORKER_THREADS` | -| `auto_start_workers` | bool | true | `AGENTSPAN_AUTO_START_WORKERS` | -| `daemon_workers` | bool | true | `AGENTSPAN_DAEMON_WORKERS` | -| `auto_register_integrations` | bool | false | `AGENTSPAN_INTEGRATIONS_AUTO_REGISTER` | -| `streaming_enabled` | bool | true | `AGENTSPAN_STREAMING_ENABLED` | -| `liveness_enabled` | bool | true | `AGENTSPAN_LIVENESS_ENABLED` | -| `liveness_stall_seconds` | float | 30.0 | `AGENTSPAN_LIVENESS_STALL_SECONDS` | -| `liveness_check_interval_seconds` | float | 10.0 | `AGENTSPAN_LIVENESS_CHECK_INTERVAL_SECONDS` | - -**Removed fields** (do not port): `server_url`, `api_key`, `auth_key`, `auth_secret` (+ `api_secret` alias), `llm_retry_count` (dead — never read), `secret_strict_mode` (dead — fail-closed credentials are unconditional), `log_level` (moved to `Configuration`), `auto_start_server`. - -**`Configuration` env resolution** (for reference — this is where connection/auth/log level live): -- Host: `CONDUCTOR_SERVER_URL` → `AGENTSPAN_SERVER_URL` → `http://localhost:8080/api` -- Auth: `CONDUCTOR_AUTH_KEY` / `CONDUCTOR_AUTH_SECRET` -- Log level: explicit param → `CONDUCTOR_LOG_LEVEL` → `AGENTSPAN_LOG_LEVEL` → derived from `debug` - ---- - -### 1.5 `RunSettings` — per-run LLM overrides - -New on this branch. Lets one invocation override the LLM settings baked into an `Agent`, without constructing a new agent. - -```python -from conductor.ai.agents import RunSettings - -result = runtime.run( - agent, "Summarize this.", - run_settings=RunSettings(model="anthropic/claude-sonnet-4-6", temperature=0.0, max_tokens=512), -) -``` - -| Field | Overrides `agentConfig` wire key | -|---|---| -| `model` | `model` | -| `temperature` | `temperature` | -| `max_tokens` | `maxTokens` | -| `reasoning_effort` | `reasoningEffort` | -| `thinking_budget_tokens` | `thinkingConfig = {"enabled": true, "budgetTokens": n}` | - -**Semantics (all MUST hold in every SDK):** -- Applied to the **serialized root `agentConfig`** after serialization, immediately before `start_agent` — pure SDK-side payload mutation; no new server field. Because start = compile+register+start atomically, the override flows into the compiled LLM tasks. -- **Null-check, not truthiness**: only fields that are *set* override — `temperature=0.0` and `max_tokens=0` are valid overrides and must apply. -- Root-only: sub-agents keep their own per-agent settings (no cascade). -- Accepted by `run`, `start`, and their async variants (and module-level convenience wrappers). A plain map with the same keys is also accepted; unknown keys are an error. -- `top_p` is deliberately absent — it does not exist in the agentConfig contract. - ---- - -## Part 2 — Method contracts - -The exact semantics of the runtime verbs. Implementations must match this table: - -| Method | Blocks | Returns | Starts tool workers | Registers agent on server | Executes | -|---|---|---|---|---|---| -| `run(agent, prompt, …)` | yes — polls to completion | **result object** (`output`, `status`, ids, token usage) | **yes** | via start (one server call compiles + registers + starts) | yes | -| `start(agent, prompt, …)` | no | **handle object** (`execution_id`, `join()`, `stop()`, `stream()`) | **yes** | via start | yes | -| `deploy(*agents, packages=?, schedules=?)` | yes | list of deployment info | **no** | **yes** — `POST /agent/deploy` per agent | no | -| `serve(*agents, packages=?, blocking=true)` | yes, until SIGINT/SIGTERM (`blocking=false` returns) | void | **yes** — registers + polls | **yes** — deploys each agent first (`serve` = `deploy` + serve) | no | -| `plan(agent)` | yes | compiled workflow def | no | no | no | -| `stream(agent, prompt, …)` | iterates | event stream | yes | via start | yes | - -### `run` — start, wait, return the result -`run(agent, prompt, *, version?, media?, session_id?, idempotency_key?, on_event?, timeout?, credentials?, context?, run_settings?) -> AgentResult` -Serializes the agent (+ `run_settings` overrides), calls `start_agent`, starts the required tool workers, then polls status to a terminal state. Returns the rich result — the output is `result.output`, **not** a bare value. `credentials=[names]` asks the server to resolve those secrets for this run. - -### `start` — fire-and-forget -`start(agent, prompt, *, version?, media?, session_id?, idempotency_key?, context?, run_settings?) -> AgentHandle` -Same start flow, but returns immediately with a handle: `handle.execution_id`, `handle.join(timeout)`, `handle.stop()`, streaming, HITL helpers. Also starts the tool workers (scheduled tool tasks must have someone to execute them). The first argument accepts an `Agent` object **or** an agent name string (with optional `version=`) for pre-deployed agents. - -### `deploy` — CI/CD registration, nothing runs -`deploy(*agents, packages=None, schedules=UNSET) -> list[DeploymentInfo]` -For each agent (passed directly or discovered from `packages`), detect the framework and call `deploy_agent` — the server compiles and registers the workflow + task definitions. **No local workers are registered or started, nothing executes.** Optional `schedules=` reconciles cron schedules for a single agent. - -### `serve` — register the agent AND serve its workers -`serve(*agents, packages=None, blocking=True) -> None` -Per agent: (1) **deploy it to the server** (same helper `deploy` uses — new on this branch, see bug fix #7), (2) register its local tool workers (task defs registered with overwrite semantics — idempotent with step 1), then start the worker manager polling. With `blocking=True`, installs SIGINT/SIGTERM handlers and blocks until interrupted; `blocking=False` returns after startup (embedding, tests). - -### Shared start flow (pseudocode) - -``` -config_json = serialize(agent) -if run_settings: config_json.update(run_settings.to_config_overrides()) -payload = {agentConfig: config_json, prompt, sessionId: session_id ?? "", media: media ?? []} -+ optional keys only when set: context, idempotencyKey, timeoutSeconds, credentials, runId, static_plan -data = agent_client.start_agent(payload) -execution_id, required_workers = data.executionId, data.requiredWorkers? -prepare_workers(agent, required_workers) # WorkerManager start — serve the tools -``` - ---- - -## Part 3 — Bug fixes on this branch (complete list) - -Each fix lists symptom → root cause → fix → **Port** (what other SDKs must do). - -### SDK bugs - -**#1 — Worker credentials failed on secured hosts (`Required credentials not found: GH_TOKEN. No execution token available`)** -*Cause:* two-fold. The credentials fetcher authenticated with `Authorization: Bearer ` — sending the raw key id as if it were a token instead of minting a JWT for `X-Authorization`. And the whole delivery design depended on a per-execution token + `POST /workers/secrets` fetch that stateless tool workers often didn't have. -*Fix:* the delivery contract was replaced entirely (see #2). The interim header fix (mint via `POST /token`, send `X-Authorization`) applies to any custom transport an SDK still has. -*Port:* never send key ids as bearer tokens; all auth goes through the ApiClient contract (§1.1). - -**#2 — Credential delivery redesigned: `runtimeMetadata` (fail-closed)** -*Cause:* the execution-token/`/workers/secrets` path was racy, added a second auth path, and broke for stateless workers. -*Fix:* new wire contract. `TaskDef.runtimeMetadata: string[]` declares the secret **names** a task type needs — the SDK stamps it at tool registration, and because worker registration uses overwrite semantics, it **re-stamps on every registration** (a sub-bug here: a plain re-register used to wipe the server-side value). At poll time the server resolves the names and delivers the **values** on the wire-only `Task.runtimeMetadata: map` (never persisted). The worker injects them for that call only and **fails** (`CredentialNotFoundError`) if a declared name wasn't delivered — it never falls back to the ambient process environment. -*Port:* add both model fields (JSON name `runtimeMetadata`), stamp on registration, resolve fail-closed at dispatch, delete the fetcher. Requires a server that persists/delivers runtimeMetadata (conductor-oss PR #1255); capability-probe in integration tests. - -**#3 — Parallel token cache in framework workers** -*Symptom:* worker processes held **two token authorities** — `ApiClient`'s and a standalone `token_utils` mint/cache used by raw `requests.post` calls to `/agent/*` (event push, tracking workflows, task injection/completion) — with separate TTL/refresh behavior. -*Fix:* new internal `agent_http` helper: an `ApiClient` **cached per `(server_url, auth_key)`**, reconstructed inside the worker process from plain strings (the constructor mints a token, so caching is mandatory), and `agent_post(path, body, read_response)` on `call_api`. Fire-and-forget callers swallow errors (return null); readers get the parsed object or null. Task-progress updates moved from raw `requests` to the native task client (`update_task`, `IN_PROGRESS`) on the **same** cached client. The standalone mint/cache was deleted (only a JWT-`exp` decoder remains). -*Port:* one `POST /token` code path in the whole SDK. Worker-side posts go through a cached authenticated client. - -**#4 — CLI tools worker was not spawn-safe (`SpawnSafetyError: worker 'run_command' is not spawn-safe`)** -*Cause:* the auto-generated `run_command` tool was a `` closure; spawned worker processes cannot import it. -*Fix:* a module-level callable class (`_CliCommandRunner`) holding plain config (allowed commands, timeout, cwd) — pickles by value. -*Port:* any auto-generated worker must be a top-level callable/class, never a closure. - -**#5 — `GPTAssistantAgent` was not spawn-safe** -*Cause:* its `call_assistant` tool was a closure over `self` inside `__init__`. -*Fix:* module-level `_AssistantCall` callable class with plain-data fields (assistant id, api key, model, instructions); the agent method delegates to it. -*Port:* same invariant as #4 for any built-in agent types. - -**#6 — `AttributeError: 'AgentConfig' object has no attribute 'liveness_stall_seconds'`** -*Cause:* the result/handle code started a `ServerLivenessMonitor` for stateful runs and read three `AgentConfig` fields that were never defined. -*Fix:* added `liveness_enabled` / `liveness_stall_seconds` (30.0) / `liveness_check_interval_seconds` (10.0) with env parsing. The monitor polls the workflow; if a task has had **no polls** for the stall window (worker died), blocking `join()`/`result()` raise `WorkerStallError` instead of hanging forever. -*Port:* implement the liveness monitor + config fields together; never ship a reader without its config. - -**#7 — `serve` did not register the agent on the server** -*Symptom:* `serve` only registered local worker task defs and polled; the agent's workflow definition was never registered, so `serve` alone couldn't make an agent runnable — users had to know to call `deploy` first. -*Fix:* `serve` now deploys each agent (same internal helper as `deploy`) before registering/starting its workers — `serve` = `deploy` + serve. Idempotent with overwrite task-def registration; same ordering `run` already used. -*Port:* add the deploy step at the top of serve's per-agent loop, covering native and framework agents. - -**#8 — `AgentConfig` duplicated connection/auth/log settings (two sources of truth)** -*Symptom:* `AgentConfig.server_url/auth_key/auth_secret/api_key` could disagree with `Configuration`; `llm_retry_count` and `secret_strict_mode` were **dead** (documented behavior that didn't exist — `secret_strict_mode` claimed to control an env fallback that the fail-closed resolver never had). -*Fix:* removed all of them (§1.4). Connection/auth from `Configuration` only; `configure()` convenience now takes a `Configuration` for connection. -*Port:* keep agent settings behaviour-only; delete dead flags rather than documenting them. - -**#9 — Log level only applied to some loggers, configured in the wrong place** -*Cause:* `AgentConfig.log_level` was applied to the agent loggers only; the rest of the SDK had a separate debug-derived level on `Configuration`. -*Fix:* `Configuration` gained a settable `log_level` (param + `CONDUCTOR_LOG_LEVEL` → `AGENTSPAN_LOG_LEVEL` env); the agent runtime reads it from there. One SDK-wide setting. -*Port:* single log-level knob on the configuration object. - -**#10 — Agent server auto-start removed** -*Symptom:* the SDK probed whether an agent server was running and could launch one — masking misconfiguration, surprising in CI, and coupling the SDK to a server distribution. -*Fix:* all detection/launch logic deleted (`runtime/server.py`, `auto_start_server` flag, `AGENTSPAN_AUTO_START_SERVER` env). A missing server is a connection error. -*Port:* do not auto-start servers from the SDK. - -**#11 — Swarm transfers: multiple transfer calls silently dropped; hand-off message lost** -*Symptom:* when the LLM emitted multiple `*_transfer_to_*` calls in one turn, only the first mattered and the rest vanished with no trace; the transfer tool's `message` argument (the hand-off note for the receiving agent) was discarded. -*Fix:* `check_transfer` is now explicitly **first-wins**: it returns `{is_transfer, transfer_to, transfer_message}` and, when extra transfers were emitted, logs a warning and surfaces them as `dropped_transfers` in the task output. The transfer no-op tool now accepts and echoes `message` (schema includes `message: str`), and the server records the hand-off as `[agent -> target]: ` in the conversation. -*Port:* transfer detection must carry the message and make dropped transfers visible — never silently discard model intent. - -**#12 — SSE streaming minted its own token** -*Cause:* the old streaming transport (and the bespoke `AgentApiClient`) had independent JWT minting. -*Fix:* superseded by `OrkesAgentClient.stream_sse`, which borrows the header from `api_client.get_authentication_headers()` per (re)connect (new public accessor — §1.1). -*Port:* streaming reuses the client token; add the accessor. - -### Deleted components (consequence of the fixes) - -| Deleted | Replaced by | -|---|---| -| `client/ai/agent_api_client.py` (bespoke transport, own JWT) | `OrkesAgentClient` on `ApiClient` | -| `runtime/http_client.py` (DX wrapper client) | `runtime.client` → `AgentClient` | -| `runtime/credentials/fetcher.py` (execution-token fetch) | `runtimeMetadata` contract (#2) | -| `runtime/server.py` (server auto-start) | — (#10) | -| `token_utils` mint/cache | `agent_http` on cached `ApiClient` (#3) | - -### Example-level fixes (Python-specific, but the invariants generalize) - -- **Missing main-module guards** (`75/76/82/83/84_*.py`): stateful examples ran orchestration at module top level; spawned worker processes re-imported the module and re-ran it (multiprocessing "safe importing of main module" error / hangs). Fixed with `if __name__ == "__main__":` guards. *Invariant: entry scripts must be safely re-importable wherever workers are separate processes.* -- **Spawn-unsafe example workers** (`82`, `92`, `16g`): tools defined as closures over the runtime or inside factory functions → hoisted to module level; `82` also moved to an **env-var-shared IPC directory** (a per-import temp dir gave every spawned worker a different directory) and rebuilds its workflow client inside the worker from `Configuration()`. -- **Missing `import sys`** (`72`, `73`): `NameError` at entry. -- **Hardcoded server URLs** (`hello_world_schedule*, settings`): now env-resolved (`CONDUCTOR_SERVER_URL` → `AGENTSPAN_SERVER_URL`). -- **Unavailable model id** (`58_scatter_gather`): hardcoded model replaced with the shared settings default. - -### Test/e2e hardening (port the patterns) - -- **Capability probe:** credential e2e tests probe whether the server persists `TaskDef.runtimeMetadata` and skip when it doesn't (older servers) — instead of failing. -- **Determinism:** credential suites use a per-test (function-scoped) runtime so worker task defs are re-registered per test — a cached module-scoped runtime masked registration regressions (verified by mutation testing). -- **Env isolation:** tests that assert "no auth configured" clear ambient `CONDUCTOR_AUTH_*` env vars (CI sets them). -- **Client-layer tests without mocks:** `AgentClient`/`agent_http` tests run against an in-process HTTP server counting `/token` mints and capturing headers — the mint-once assertion is the guard against reintroducing per-call minting. - ---- - -## Porting order - -1. `ApiClient` accessor + `Configuration` env/log changes (§1.1, #9) -2. `AgentClient` + factory; `AgentRuntime` on it; delete bespoke transports + auto-start (§1.2–1.3, #10, #12) -3. Credentials via `runtimeMetadata` (#1, #2) -4. Worker-side single token authority (#3) -5. `RunSettings` + verb contract incl. `serve` = deploy + serve (§1.5, Part 2, #7) -6. Spawn-safety/registration invariants, liveness, swarm transfer fixes (#4–#6, #11) -7. Deletion sweep + test patterns (tables above) - -Definition of done and per-requirement acceptance criteria: see `docs/design/AGENT_SDK_PORTING_SPEC.md`. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..ad6ef0888 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,36 @@ +# Python SDK documentation + +Build durable workflow workers and Conductor agents with Python. These guides +cover OSS and Orkes; pages call out capabilities that require Orkes. + +## Start here + +| Goal | Guide | Expected result | +|---|---|---| +| Connect to a server | [Server setup](server-setup.md) and [connection/authentication](connection-authentication.md) | The SDK can reach an OSS or Orkes API endpoint. | +| Build a workflow and worker | [Core quickstart](core-quickstart.md) | The hello-world workflow prints its result. | +| Build a Conductor agent | [Agent quickstart](agents/getting-started.md) | An LLM-backed agent completes through Conductor. | + +## Build + +- [Workflows](workflows.md), [workflow lifecycle](workflow-lifecycle.md), and [workers](workers.md) +- [Workflow testing](workflow-testing.md), [schemas](schema-client.md), and [schedules/events](schedules-events.md) +- [Conductor agents](agents/README.md), [tools](agents/concepts/tools.md), and [framework bridges](agents/README.md#framework-bridges) +- [Recommended examples](examples.md); [examples/README.md](../examples/README.md) is the full catalog. + +## Operate + +- [Reliability](reliability.md), [security](security.md), and [deployment/scaling](deployment-scaling.md) +- [Metrics and logging](observability.md) and [debugging](debugging.md) + +## Reference and upgrades + +- [Core API map](api-map.md), [compatibility](compatibility.md), and [upgrading](upgrading.md) +- [Agent runtime](agents/reference/runtime.md), [control plane](agents/reference/client.md), and [agent definition](agents/reference/agent-definition.md) +- [Java/Python documentation parity](documentation-parity.md) — intentional Python mappings and unsupported Java-only surfaces. + +## Documentation conventions + +Primary guides follow the [documentation standard](documentation-standard.md). +Provider credentials belong on the Conductor server or its secret provider, not +in workflow input, example source, or a client-side `.env` committed to Git. diff --git a/docs/agents/README.md b/docs/agents/README.md index 0d7982897..37a7b67a2 100644 --- a/docs/agents/README.md +++ b/docs/agents/README.md @@ -1,39 +1,46 @@ # Conductor Agent Python SDK -> Ships as part of [`conductor-python`](https://pypi.org/project/conductor-python/) — install with the `agents` extra -> (`pip install 'conductor-python[agents]'`) — you're in the right place. +Build durable Python AI agents on Conductor. Agents can use local Python tools, +wait for people, execute dynamic plans, and recover after process restarts because +Conductor persists execution state. -Long-running, dynamic plan-execute, and event-driven AI agents in Python. You write plain Python; Conductor Agent compiles your agent into a Conductor workflow that runs on a server — with automatic retries, durable state, human-in-the-loop pauses, streaming, scheduling, dynamic plan-execute, and full execution history. +## Install -```python -from conductor.ai.agents import Agent, AgentRuntime +```shell +pip install 'conductor-python[agents]' +``` -agent = Agent(name="greeter", model="anthropic/claude-sonnet-4-6", - instructions="You are a friendly assistant.") +Requirements: Python 3.10+ and a Conductor server with an LLM provider configured +server-side. Replace example model names with a model enabled on that server. -with AgentRuntime() as runtime: - result = runtime.run(agent, "Say hello.") - print(result.output) -``` +## Start here -## Docs +- [Getting started](getting-started.md) — configure a server and run a basic agent. +- [Deploy · Serve · Run · Plan](concepts/deploy-serve-run.md) — choose a runtime mode. +- [Scheduling](concepts/scheduling.md) — manage deployed-agent schedules. -- [Getting started](getting-started.md) — install, env vars, and a running agent in under 30 seconds. -- [Writing agents](writing-agents.md) — the `Agent` class and `@agent`, tools, multi-agent strategies, handoffs, guardrails, termination, callbacks, streaming + HITL, schedules, stateful and instance agents. -- [Framework agents](framework-agents.md) — run agents authored in the OpenAI Agents SDK, LangChain, LangGraph, or the Claude Agent SDK. -- [Advanced](advanced.md) — runtime config, the control-plane `AgentClient`, deploy vs serve vs run vs plan, structured output, credentials, plans (`PLAN_EXECUTE`), skills. -- [API reference](api-reference.md) — the public API surface in one place. +## Build agents -## Import surface +- [Agents](concepts/agents.md), [tools](concepts/tools.md), and [multi-agent](concepts/multi-agent.md) +- [Guardrails](concepts/guardrails.md), [termination](concepts/termination.md), [callbacks](concepts/callbacks.md) +- [Stateful agents](concepts/stateful.md), [streaming and HITL](concepts/streaming-hitl.md), and [structured output](concepts/structured-output.md) -Everything public is importable from `conductor.ai.agents`: +## Framework bridges -```python -from conductor.ai.agents import Agent, AgentRuntime, tool, agent -``` +- [Google ADK](frameworks/google-adk.md), [LangChain](frameworks/langchain.md), and [LangGraph](frameworks/langgraph.md) +- [OpenAI Agents SDK](frameworks/openai.md) and [Claude Agent SDK](frameworks/claude-agent-sdk.md) -A small OpenAI-Agents-compatible shim is also exposed at the top level: +## Operate and inspect -```python -from conductor.ai import Runner, function_tool # drop-in for `agents.Runner` -``` +- [Runtime reference](reference/runtime.md), [control-plane reference](reference/client.md), and [API map](reference/api.md) +- [Agent-definition fields](reference/agent-definition.md) and [configuration contract](reference/agent-schema.md) + +## What Conductor adds + +| Capability | Conductor agent runtime | +|---|---| +| Process recovery | Durable workflow state resumes from completed work. | +| Python tools | Tools run as independently scalable Conductor worker tasks. | +| Long-running work | Human approval, schedules, and events do not occupy application threads. | +| Dynamic execution | Plans become durable, inspectable sub-workflows. | +| Observability | Inputs, outputs, tool calls, retries, and status share one execution record. | diff --git a/docs/agents/advanced.md b/docs/agents/advanced.md index 807c131b1..c8e43aec9 100644 --- a/docs/agents/advanced.md +++ b/docs/agents/advanced.md @@ -1,326 +1,8 @@ -# Advanced +# Advanced Conductor-agent usage -- [Runtime init and config](#runtime-init-and-config) -- [run vs start vs stream vs deploy vs serve vs plan](#run-vs-start-vs-stream-vs-deploy-vs-serve-vs-plan) -- [The control-plane AgentClient](#the-control-plane-agentclient) -- [Structured output](#structured-output) -- [Credentials and secrets](#credentials-and-secrets) -- [Plans and PLAN_EXECUTE](#plans-and-plan_execute) -- [Schedules](#schedules) -- [Skills](#skills) +This compatibility page has moved to: -## Runtime init and config - -`AgentRuntime` is the entry point. Use it as a context manager so workers shut down -cleanly. Server connection comes from the standard Conductor `Configuration` — -the same object every other client uses — which resolves `CONDUCTOR_SERVER_URL` -(falling back to `AGENTSPAN_SERVER_URL`) and `CONDUCTOR_AUTH_KEY`/`CONDUCTOR_AUTH_SECRET` -from the environment when not passed explicitly. - -```python -from conductor.ai.agents import AgentRuntime, AgentConfig -from conductor.client.configuration.configuration import Configuration - -# From env (CONDUCTOR_SERVER_URL → AGENTSPAN_SERVER_URL, CONDUCTOR_AUTH_*) -with AgentRuntime() as runtime: - runtime.run(agent, "hi") - -# Explicit Configuration -with AgentRuntime(Configuration(server_api_url="https://prod:8080/api")) as runtime: - ... - -# Runtime behaviour knobs (workers, streaming, log level) via AgentConfig -settings = AgentConfig.from_env() -settings.log_level = "DEBUG" -with AgentRuntime(settings=settings) as runtime: - ... -``` - -`AgentConfig` is a dataclass; `from_env()` reads the `AGENTSPAN_*` environment -variables (full list in [Getting started](getting-started.md#environment-variables)). -It carries runtime *behaviour* settings only — server connection always comes from -the `Configuration`. - -### Module-level convenience functions - -For one-off scripts, top-level functions use a shared singleton runtime: - -```python -import conductor.ai.agents as ag -from conductor.client.configuration.configuration import Configuration - -# Connection/auth via a Configuration; agent-behaviour knobs via config=AgentConfig(...). -ag.configure(configuration=Configuration(server_api_url="https://prod:8080/api")) # before first run -result = ag.run(agent, "Hello!") -ag.shutdown() # explicit cleanup; not required for simple scripts -``` - -`configure(...)` must be called before the first `run`/`start`/`stream`. Available: -`run`, `run_async`, `start`, `start_async`, `stream`, `stream_async`, `resume`, -`resume_async`, `deploy`, `deploy_async`, `serve`, `plan`, `configure`, `shutdown`. - -## run vs start vs stream vs deploy vs serve vs plan - -| Call | Blocks? | Returns | When | -|---|---|---|---| -| `runtime.run(agent, prompt)` | yes | `AgentResult` | Simplest case — run and get the answer | -| `runtime.start(agent, prompt)` | no | `AgentHandle` | Fire-and-forget; poll/control later | -| `runtime.stream(agent, prompt)` | iterates | `AgentStream` | Watch events live; drive HITL | -| `runtime.deploy(*agents)` | yes | `list[DeploymentInfo]` | CI/CD: compile + register, no workers, no execution | -| `runtime.serve(*agents)` | yes (blocks) | — | Register agent(s) **and** serve workers; long-lived, polls until interrupted (`serve` = `deploy` + serve) | -| `runtime.plan(agent)` | yes | `dict` | Compile to a workflow def without running anything | - -`run`/`start`/`stream` accept `media=`, `session_id=`, `idempotency_key=`, -`credentials=`, and extra `**kwargs` as workflow input. `run`/`run_async` also accept -`on_event=` to stream while running synchronously, `timeout=`, and `context=`. -`run`/`start` (and their async variants) also accept `run_settings=` — a `RunSettings` -(or dict) that overrides the agent's `model`/`temperature`/`max_tokens`/… for that one -call. See [API reference](api-reference.md#per-run-overrides--runsettings). - -`plan(agent)` returns `{"workflowDef": ..., "requiredWorkers": ...}` — useful to -inspect the compiled Conductor workflow: - -```python -result = runtime.plan(agent) -print(result["workflowDef"]["name"]) -print(result["workflowDef"]["tasks"]) -``` - -### Deploy once, serve separately (production) - -```python -# CI/CD step: -runtime.deploy(agent) -# CLI alternative: -# agentspan deploy --package my_pkg.my_module -# agentspan deploy --path ./agents --agents greeter,support - -# Long-lived worker process: -runtime.serve(agent) # registers the agent (idempotent) + blocks, polling for tool tasks -``` - -`serve` also registers the agent on the server, so the explicit `deploy` step above -is optional — it's still useful to register/upgrade the workflow from CI independently -of the worker rollout. - -`resume(execution_id, agent)` re-attaches to a previously `start`ed execution and -re-registers its tool workers (e.g. after a process restart): - -```python -handle = runtime.start(agent, "Long job") -eid = handle.execution_id -# later, even after a restart: -handle = runtime.resume(eid, agent) -result = handle.join(timeout=120) -``` - -## The control-plane AgentClient - -`runtime.client` is the **control-plane** `AgentClient` (formerly `AgentHttpClient` — -the old name is kept as an alias). It talks to the `/agent/*` HTTP endpoints directly: -compile, deploy, start, run, schedule, status, respond, stop, signal, SSE. It is -control-plane only — its `run`/`start` do **not** register or poll local `@tool` -workers, so use it for agents whose tools are all server-side (HTTP/MCP/built-in) or -already deployed. - -```python -with AgentRuntime() as runtime: - client = runtime.client - - result = client.run(agent, "Hello") # compile + start + poll - handle = client.start(agent, "Long job") - infos = client.deploy(agent) # compile + register - - # Cron lifecycle (same surface as runtime.schedules_client()): - client.schedule(agent, [nightly]) # reconcile schedules - client.schedules.pause("agent-nightly") -``` - -Key methods: `run`/`run_async`, `start`/`start_async`, `deploy`/`deploy_async`, -`schedule(agent, schedules)`, `get_status`, `respond`, `stop`, `signal`, -`stream_sse`, and `.schedules` (the schedule lifecycle — `pause`/`resume`/`delete`/ -`run_now`/`preview_next`/`reconcile`, now carried by `SchedulerClient` itself). Both -sync and async forms exist. Most users call `runtime.run/start/deploy` instead, -which add local-worker management on top of this client. - -The raw `/agent/*` HTTP transport behind this client is -`conductor.client.ai.AgentApiClient` — also reachable without the agents layer via -`OrkesClients.get_agent_client()` (and `get_scheduler_client()` for the cron -lifecycle). `AgentClient` composes that transport and adds the agent-level surface. - -## Structured output - -Pass `output_type=` a Pydantic model (or dataclass) to get a typed, validated result. -Pydantic is only needed when you use this feature. - -```python -from pydantic import BaseModel -from conductor.ai.agents import Agent, AgentRuntime, tool - -class WeatherReport(BaseModel): - city: str - temperature: float - condition: str - recommendation: str - -@tool -def get_weather(city: str) -> dict: - """Get weather data.""" - return {"city": city, "temp_f": 72, "condition": "Sunny"} - -agent = Agent(name="reporter", model="openai/gpt-4o", - tools=[get_weather], output_type=WeatherReport, - instructions="Report the weather with a recommendation.") - -with AgentRuntime() as runtime: - result = runtime.run(agent, "What's the weather in NYC?") - print(result.output) # conforms to WeatherReport's schema -``` - -## Credentials and secrets - -Store secrets in the server's credential store (never in code), then declare them per -tool with `credentials=[...]`. Inside the tool, read the injected value with -`get_secret(name)`. - -```python -from conductor.ai.agents import tool, get_secret - -@tool(credentials=["OPENAI_API_KEY"]) -def call_openai(prompt: str) -> str: - """Call OpenAI directly using a stored credential.""" - key = get_secret("OPENAI_API_KEY") # only works inside a credentials-aware tool - ... -``` - -You can also declare credentials at the agent level (`Agent(..., credentials=[...])`), -and HTTP/built-in tools resolve `${CRED_NAME}` placeholders in headers from the same -store at execution time. Pass `credentials=[...]` to `runtime.run(...)` to supply -credential names for a specific execution. - -`get_secret` raises `CredentialNotFoundError` when the credential is absent. Other -credential errors: `CredentialAuthError`, `CredentialRateLimitError`, -`CredentialServiceError`. Store a credential via the CLI: - -```bash -agentspan credentials set OPENAI_API_KEY sk-... -``` - -## Plans and PLAN_EXECUTE - -`Strategy.PLAN_EXECUTE` runs a planner agent that emits a JSON plan, which is then -executed deterministically against a fixed tool set. Build the harness with the -`plan_execute` helper, or the `Agent` named-slot API. - -```python -from conductor.ai.agents import plan_execute - -harness = plan_execute( - "report_builder", - tools=[create_directory, write_file, check_word_count], - planner_instructions="Plan a multi-section report, then write each section.", - model="openai/gpt-4o", -) -result = runtime.run(harness, "Write a report on Rust adoption.") -``` - -Or directly: - -```python -from conductor.ai.agents import Agent, Strategy - -planner = Agent(name="rb_planner", model="openai/gpt-4o", instructions="Plan it.") -harness = Agent(name="report_builder", strategy=Strategy.PLAN_EXECUTE, - planner=planner, tools=[write_file, check_word_count]) -``` - -`PLAN_EXECUTE` requires `planner=` (the agent that emits the plan) and `tools=` on the -parent (the canonical executable tools); `fallback=` is optional. - -### Static plans (skip the planner) - -Build a deterministic plan in Python with the typed builders and pass it to `run`: - -```python -from conductor.ai.agents.plans import Plan, Step, Op, Generate, Validation, Ref - -plan = Plan( - steps=[ - Step("setup", operations=[Op("create_directory", args={"path": "out"})]), - Step("write", depends_on=["setup"], parallel=True, operations=[ - Op("write_file", generate=Generate( - instructions="Write the introduction.", - output_schema='{"path": "out/intro.md", "content": "..."}')), - ]), - Step("summarize", depends_on=["write"], operations=[ - Op("summarize", args={"document": Ref("write")}), # wire a prior step's output - ]), - ], - validation=[Validation("check_word_count", args={"path": "out/intro.md", "min_words": 200})], -) - -runtime.run(harness, "build it", plan=plan) -``` - -`Op` takes either `args=` (literal) or `generate=` (LLM-generated args). `Ref("step")` -injects an upstream step's output (the step must be in `depends_on`). `Step.parallel` -runs a step's operations concurrently; `depends_on` expresses cross-step concurrency. - -### Planner context - -Ground the planner with reference documents via `planner_context=` — inline text or a -URL fetched at planner-run time: - -```python -from conductor.ai.agents.plans import Context - -harness = plan_execute( - "kyc", tools=[...], - planner_instructions="Follow the KYC process.", - planner_context=[ - "Tier-1 customers skip manual review.", # inline string - Context(url="https://wiki/kyc-rules", headers={"Authorization": "Bearer ${KYC_TOKEN}"}), - ], -) -``` - -## Schedules - -Attach cron schedules at deploy time, or manage them through the schedule client. - -```python -from conductor.ai.agents import Schedule - -nightly = Schedule(name="nightly", cron="0 0 * * *", timezone="UTC", - input={"prompt": "Daily summary."}) - -runtime.deploy(agent, schedules=[nightly]) # upsert; [] purges; omit leaves as-is - -sc = runtime.schedules_client() # or runtime.client.schedules -sc.get_all_schedules(workflow_name=agent.name) # list — source-of-truth read -sc.pause("greeter-nightly", reason="ship freeze") -print(sc.preview_next("0 0 * * *", n=5)) # next 5 fire times (epoch ms) - -from conductor.ai.agents.schedule import schedules -schedules.run_now("greeter-nightly", runtime=runtime) # fire once -> execution id -``` - -## Skills - -Load an agentskills.io skill directory (with a `SKILL.md`) as an `Agent`: - -```python -from conductor.ai.agents import skill, load_skills - -researcher = skill("./skills/deep-research", model="openai/gpt-4o", - params={"rounds": 3}) -all_skills = load_skills("./skills", model="openai/gpt-4o") # dict: name -> Agent - -runtime.run(researcher, "Research durable execution engines.") -``` - -`skill(path, model="", agent_models=None, search_path=None, params=None)` returns an -ordinary `Agent` you can run, compose (e.g. via `agent_tool`), deploy, and serve. -Sub-agent files (`*-agent.md`), `scripts/`, and resource files are discovered -automatically; cross-skill references resolve from sibling and `~/.agents/skills` -directories plus any `search_path`. +- [Deploy · Serve · Run · Plan](concepts/deploy-serve-run.md) +- [Runtime reference](reference/runtime.md) and [control plane](reference/client.md) +- [Structured output](concepts/structured-output.md), [scheduling](concepts/scheduling.md), and [stateful agents](concepts/stateful.md) +- [Agent configuration contract](reference/agent-schema.md) diff --git a/docs/agents/api-reference.md b/docs/agents/api-reference.md index b1579b185..133763213 100644 --- a/docs/agents/api-reference.md +++ b/docs/agents/api-reference.md @@ -1,348 +1,9 @@ -# API reference +# Conductor-agent API reference -The public surface, importable from `conductor.ai.agents` unless noted. This is a -reference; for usage see [Writing agents](writing-agents.md), [Framework -agents](framework-agents.md), and [Advanced](advanced.md). +The reference is now split for easier navigation: -- [AgentRuntime](#agentruntime) -- [Agent / @agent](#agent) -- [Tools](#tools) and [built-in tools](#built-in-tools) -- [Guardrails](#guardrails) -- [Termination](#termination) -- [Handoffs](#handoffs) -- [TextGate](#textgate) -- [Schedules](#schedules) -- [Results, handles, streams, events](#results-handles-streams-events) -- [CallbackHandler](#callbackhandler) -- [AgentClient](#agentclient) -- [Config and credentials](#config-and-credentials) - -## AgentRuntime - -`AgentRuntime(configuration=None, *, settings=None)` — `configuration` is the -standard Conductor `Configuration` (host + auth; defaults to `Configuration()`, -which resolves `CONDUCTOR_SERVER_URL` → `AGENTSPAN_SERVER_URL` and -`CONDUCTOR_AUTH_*` from the environment); `settings` is an optional `AgentConfig` -with runtime behaviour knobs (its connection fields are ignored). - -Context manager (sync and async: `with` / `async with`). - -| Method | Signature | Purpose | -|---|---|---| -| `run` | `(agent, prompt=None, *, version=None, media=None, session_id=None, idempotency_key=None, on_event=None, timeout=None, credentials=None, context=None, run_settings=None, **kwargs) -> AgentResult` | Start, wait for completion, return the result (also starts workers) | -| `run_async` | same as `run` | Async run | -| `start` | `(agent, prompt=None, *, version=None, media=None, session_id=None, idempotency_key=None, context=None, run_settings=None, **kwargs) -> AgentHandle` | Fire-and-forget; returns a handle (also starts workers) | -| `start_async` | same as `start` | Async start | -| `stream` | `(agent=None, prompt=None, *, version=None, handle=None, media=None, session_id=None, **kwargs) -> AgentStream` | Stream events | -| `stream_async` | same as `stream` | `-> AsyncAgentStream` | -| `deploy` | `(*agents, packages=None, schedules=_UNSET) -> list[DeploymentInfo]` | Compile + register agent(s) on the server; does **not** start workers | -| `deploy_async` | same | Async deploy | -| `serve` | `(*agents, packages=None, blocking=True) -> None` | Register agent(s) on the server **and** serve/poll their workers (`serve` = `deploy` + serve) | -| `plan` | `(agent) -> dict` | Compile to workflow def | -| `resume` | `(execution_id, agent, *, timeout=None) -> AgentHandle` | Re-attach + re-register workers | -| `resume_async` | same | Async resume | -| `prepare` | `(agent) -> None` | Pre-register workers, no execution | -| `get_status` | `(execution_id) -> AgentStatus` | Execution status | -| `respond` | `(execution_id, output) -> None` | Complete a human task | -| `approve` / `reject` | `(execution_id)` / `(execution_id, reason="")` | HITL approve / reject | -| `send_message` | `(execution_id, message) -> None` | Push to workflow message queue | -| `pause` / `cancel` / `stop` | `(execution_id[, reason])` | Lifecycle control | -| `signal` | `(execution_id, message) -> None` | Inject persistent context | -| `shutdown` | `() -> None` | Stop all workers | -| `client` (property) | `-> AgentClient` | Control-plane client | -| `schedules_client` | `() -> SchedulerClient` | Shared schedule client | - -Async variants exist for status/respond/approve/reject/send/stop/shutdown -(`*_async`). Module-level wrappers using a singleton runtime: `run`, `run_async`, -`start`, `start_async`, `stream`, `stream_async`, `resume`, `resume_async`, `deploy`, -`deploy_async`, `serve`, `plan`, `configure`, `shutdown`. - -### Per-run overrides — `RunSettings` - -`run` / `start` (and their async variants and the module-level wrappers) accept -`run_settings=` to override an agent's LLM settings for a single invocation -without rebuilding the `Agent`: - -```python -from conductor.ai.agents import RunSettings - -runtime.run( - agent, - "Summarize this.", - run_settings=RunSettings(model="anthropic/claude-sonnet-4-6", temperature=0.0, max_tokens=512), -) -``` - -`RunSettings(model=None, temperature=None, max_tokens=None, reasoning_effort=None, -thinking_budget_tokens=None)` — only the fields you set override; unset fields keep -the agent's own values (so `temperature=0.0` is honored). A plain `dict` with the -same keys is also accepted. Overrides apply to the root agent's config; sub-agents -keep their own settings. - -## Agent - -`Agent(name, model="", instructions="", tools=None, agents=None, -strategy=Strategy.HANDOFF, router=None, output_type=None, guardrails=None, -memory=None, dependencies=None, max_turns=25, max_tokens=None, timeout_seconds=0, -temperature=None, reasoning_effort=None, stop_when=None, termination=None, -handoffs=None, allowed_transitions=None, introduction=None, metadata=None, -local_code_execution=False, allowed_languages=None, allowed_commands=None, -code_execution=None, cli_commands=False, cli_allowed_commands=None, cli_config=None, -enable_planning=False, callbacks=None, include_contents=None, -thinking_budget_tokens=None, required_tools=None, gate=None, base_url=None, -credentials=None, stateful=False, context_window_budget=None, prefill_tools=None, -fallback_max_turns=None, synthesize=True, masked_fields=None, planner=None, -fallback=None, planner_context=None)` - -- `name` must match `[a-zA-Z_][a-zA-Z0-9_-]*`. -- `model` is `"provider/model"`; empty means inherit from parent or treat as an - external workflow reference. -- `instructions` may be a string, a callable returning a string, or a `PromptTemplate`. -- `strategy` accepts a `Strategy` value or a string. -- Properties: `.is_claude_code`, `.external`. `a >> b` builds a sequential pipeline. - -Classmethod: `Agent.from_instance(instance, name=None)` — resolve `@agent` methods on -an object into one `Agent` (by `name`) or `list[Agent]` (all). `@tool`/`@guardrail` -methods on the instance are auto-attached. - -`@agent(func=None, *, name=None, model="", tools=None, guardrails=None, agents=None, -strategy=Strategy.HANDOFF, max_turns=25, max_tokens=None, temperature=None, -metadata=None, credentials=None, context_window_budget=None, ...)` — register a -function as an agent. The docstring is the instructions; returning a string gives -dynamic instructions. - -`Strategy` enum: `HANDOFF`, `SEQUENTIAL`, `PARALLEL`, `ROUTER`, `ROUND_ROBIN`, -`RANDOM`, `SWARM`, `MANUAL`, `PLAN_EXECUTE`. - -`PromptTemplate(name, variables={}, version=None)` — reference a server-side template. - -`scatter_gather(name, worker, *, model=None, instructions="", tools=None, -retry_count=None, retry_delay_seconds=None, fail_fast=False, **kwargs) -> Agent`. - -## Tools - -`@tool(func=None, *, name=None, external=False, approval_required=False, -timeout_seconds=None, guardrails=None, credentials=None, stateful=False, -max_calls=None, retry_count=2, retry_delay_seconds=2, -retry_policy="linear_backoff")` — register a function as a tool. Type hints + -docstring produce the schema. Attaches `_tool_def`. - -`ToolDef` fields: `name`, `description=""`, `input_schema={}`, `output_schema={}`, -`func`, `approval_required=False`, `timeout_seconds=None`, `tool_type="worker"`, -`config={}`, `guardrails=[]`, `credentials=[]`, `stateful=False`, `max_calls=None`, -`retry_count=2`, `retry_delay_seconds=2`, `retry_policy="linear_backoff"`. Method -`ToolDef.call(**kwargs) -> PrefillToolCall`. - -`ToolContext` fields: `session_id`, `execution_id`, `agent_name`, `metadata`, -`dependencies`, `state`. Declare a `context: ToolContext` parameter to receive it. - -`PrefillToolCall(tool_name, arguments, tool_def=None)` — a pre-declared tool call for -`Agent(prefill_tools=[...])`, created via `tool_def.call(...)`. - -Helpers: `get_tool_def(obj) -> ToolDef`, `get_tool_defs(tools) -> list[ToolDef]`. -`ToolRegistry.register_tool_workers(tools, agent_name, domain=None, -agent_stateful=False)` (used internally by the runtime). - -### Built-in tools - -- `http_tool(name, description, url, method="GET", headers=None, input_schema=None, accept=["application/json"], content_type="application/json", credentials=None)` -- `api_tool(url, name=None, description=None, headers=None, tool_names=None, max_tools=64, credentials=None)` -- `mcp_tool(server_url, name=None, description=None, headers=None, tool_names=None, max_tools=64, credentials=None)` -- `human_tool(name, description, input_schema=None)` -- `image_tool(name, description, llm_provider, model, input_schema=None, **defaults)` -- `audio_tool(name, description, llm_provider, model, input_schema=None, **defaults)` -- `video_tool(name, description, llm_provider, model, input_schema=None, **defaults)` -- `pdf_tool(name="generate_pdf", description="...", input_schema=None, **defaults)` -- `index_tool(name, description, vector_db, index, embedding_model_provider, embedding_model, namespace="default_ns", chunk_size=None, chunk_overlap=None, dimensions=None, input_schema=None)` -- `search_tool(name, description, vector_db, index, embedding_model_provider, embedding_model, namespace="default_ns", max_results=5, dimensions=None, input_schema=None)` -- `wait_for_message_tool(name, description, batch_size=1, blocking=True)` -- `agent_tool(agent, name=None, description=None, retry_count=None, retry_delay_seconds=None, optional=None)` - -OCG (from `conductor.ai.agents.ocg`): -`ocg_agent(*, model, url, name="ocg_agent", credential=None, instructions=None, -max_turns=10, query=True, entities=True, memory=True) -> Agent`; -`ocg_tools(*, url, credential=None, query=True, entities=True, memory=True) -> -list[ToolDef]`; `OCG_SYSTEM_PROMPT`. - -## Guardrails - -`@guardrail(func=None, *, name=None)` — register a `(str) -> GuardrailResult` function. - -`Guardrail(func=None, position=Position.OUTPUT, on_fail=OnFail.RETRY, name=None, -max_retries=3)`. `func=None` + `name=` makes an external guardrail. - -`RegexGuardrail(patterns, *, mode="block", position=Position.OUTPUT, -on_fail=OnFail.RETRY, name=None, message=None, max_retries=3)` — `mode="block"` fails -on match, `"allow"` fails on no match. - -`LLMGuardrail(model, policy, *, position=Position.OUTPUT, on_fail=OnFail.RETRY, -name=None, max_retries=3, max_tokens=None)` — LLM judges content against `policy` -(requires `litellm` at evaluation time). - -`GuardrailResult(passed, message="", fixed_output=None)`. -`OnFail`: `RETRY`, `RAISE`, `FIX`, `HUMAN`. `Position`: `INPUT`, `OUTPUT`. -`GuardrailDef(name, description, func)`. - -## Termination - -Composable with `&` (all) and `|` (any). All take a context dict and return a -`TerminationResult(should_terminate, reason="")`. - -- `TextMentionTermination(text, *, case_sensitive=False)` -- `StopMessageTermination(stop_message="TERMINATE")` -- `MaxMessageTermination(max_messages)` -- `TokenUsageTermination(max_total_tokens=None, max_prompt_tokens=None, max_completion_tokens=None)` -- `TerminationCondition` (base) - -## Handoffs - -For `strategy="swarm"`, in `handoffs=[...]`. All carry `target`. - -- `OnToolResult(target, tool_name="", result_contains=None)` — after a named tool runs (optionally only if the result contains a substring). -- `OnTextMention(target, text="")` — LLM output contains `text` (case-insensitive). -- `OnCondition(target, condition=...)` — `condition(context) -> bool`. -- `HandoffCondition` (base). - -## TextGate - -From `conductor.ai.agents.gate`: `TextGate(text, case_sensitive=True)` — stop a `>>` -pipeline after this agent when its output contains `text`. Compiled server-side. - -## Schedules - -`Schedule(name, cron, timezone="UTC", input={}, catchup=False, paused=False, -start_at=None, end_at=None, description=None)` — `cron` is a 5- or 6-field expression. - -`ScheduleInfo` (read model) fields include `name`, `short_name`, `agent`, `cron`, -`timezone`, `input`, `paused`, `catchup`, `next_run`, `create_time`, `update_time`, ... - -The schedule lifecycle lives on `SchedulerClient` itself (via -`runtime.schedules_client()`, `runtime.client.schedules`, or -`OrkesClients.get_scheduler_client()`): - -| Method | Signature | -|---|---| -| `pause` / `resume` | `(wire_name[, reason])` / `(wire_name)` | -| `delete` | `(wire_name) -> None` | -| `run_now` | `(info: ScheduleInfo) -> str` (execution_id) | -| `preview_next` | `(cron, n=5, start_at=None, end_at=None) -> list[int]` | -| `reconcile` | `(agent_name, desired: list[Schedule] | None) -> None` | - -Reads/writes/lists use the native source-of-truth methods: `get_schedule(wire) -> -WorkflowSchedule | None`, `save_schedule(SaveScheduleRequest)`, -`get_all_schedules(workflow_name=...) -> list[WorkflowSchedule]`. The mapped -`ScheduleInfo` view is returned by the module-level `schedules.list/get`. - -Errors: `ScheduleError`, `ScheduleNameConflict`, `ScheduleNotFound`, -`InvalidCronExpression`. - -## Results, handles, streams, events - -### AgentResult - -Fields: `output`, `execution_id`, `correlation_id`, `messages`, `tool_calls`, -`status` (`Status`), `token_usage` (`TokenUsage`), `metadata`, `finish_reason` -(`FinishReason`), `error`, `events`, `sub_results`. Properties: `is_success()`, -`is_failed()`, `is_rejected()`. Method: `print_result()`. - -`Status`: `COMPLETED`, `FAILED`, `TERMINATED`, `TIMED_OUT`. -`FinishReason`: `STOP`, `LENGTH`, `TOOL_CALLS`, `ERROR`, `CANCELLED`, `TIMEOUT`, -`GUARDRAIL`, `REJECTED`, `STOPPED`. -`TokenUsage`: `prompt_tokens`, `completion_tokens`, `total_tokens`, `reasoning_tokens`. -`DeploymentInfo`: `registered_name`, `agent_name`. - -### AgentHandle - -Fields: `execution_id`, `correlation_id`, `run_id`, `is_resumed`. - -| Method | Signature | Notes | -|---|---|---| -| `get_status` | `() -> AgentStatus` | | -| `stream` | `() -> AgentStream` | | -| `join` | `(timeout=None) -> AgentResult` | block until terminal | -| `respond` | `(output: dict, *, event=None) -> None` | answer a `human_tool` | -| `approve` | `(*, event=None) -> None` | approve pending tool | -| `reject` | `(reason="", *, event=None) -> None` | reject pending tool | -| `send` | `(message: str, *, event=None) -> None` | multi-turn message | -| `pause` / `resume` / `cancel` / `stop` | `()` / `()` / `(reason="")` / `()` | lifecycle | - -The `event=` parameter targets a specific pending pause (event-targeted HITL). Every -method has an `*_async` counterpart (e.g. `approve_async`, `join_async`). - -`AgentStatus` fields: `execution_id`, `is_complete`, `is_running`, `is_waiting`, -`output`, `status`, `reason`, `current_task`, `messages`, `pending_tool`. - -### AgentStream / AsyncAgentStream - -Iterable (sync `for` / async `for`) yielding `AgentEvent`. Fields: `handle`, `events`, -`result`, `execution_id`. Methods: `get_result()`, and HITL `respond`/`approve`/ -`reject`/`send` (each with `*, event=None`). `AsyncAgentStream`'s methods are async. - -### AgentEvent / EventType - -`AgentEvent` fields: `type`, `content`, `tool_name`, `args`, `result`, `target`, -`output`, `execution_id`, `guardrail_name`. - -`EventType`: `THINKING`, `TOOL_CALL`, `TOOL_RESULT`, `HANDOFF`, `WAITING`, `MESSAGE`, -`ERROR`, `DONE`, `GUARDRAIL_PASS`, `GUARDRAIL_FAIL`. - -## CallbackHandler - -Subclass and override any of: `on_agent_start`, `on_agent_end`, `on_model_start`, -`on_model_end`, `on_tool_start`, `on_tool_end`. Each is `(self, **kwargs) -> -Optional[dict]`: return `None` to continue, a non-empty dict to short-circuit and -override. Pass instances via `Agent(callbacks=[...])`; they chain in list order. - -## AgentClient - -The `/agent/*` control-plane client. `AgentClient` is an interface -(`conductor.client.agent_client`) implemented by `OrkesAgentClient` -(`conductor.client.orkes.orkes_agent_client`), following the same pattern as -`WorkflowClient`/`OrkesWorkflowClient` and built on the shared `ApiClient` -token machinery. Reach it via `runtime.client` or -`OrkesClients(configuration).get_agent_client()`. Every method has an `*_async` -counterpart. - -| Method | Signature | Purpose | -|---|---|---| -| `start_agent` | `(payload) -> dict` | POST /agent/start | -| `deploy_agent` | `(payload) -> dict` | POST /agent/deploy | -| `compile_agent` | `(payload) -> dict` | POST /agent/compile | -| `get_status` | `(execution_id) -> dict` | GET /agent/{id}/status | -| `get_execution` | `(execution_id) -> dict` | GET /agent/execution/{id} | -| `list_executions` | `(params=None) -> dict` | GET /agent/executions | -| `respond` | `(execution_id, body) -> None` | POST /agent/{id}/respond | -| `stop` | `(execution_id) -> None` | POST /agent/{id}/stop | -| `signal` | `(execution_id, message) -> None` | POST /agent/{id}/signal | -| `stream_sse` | `(execution_id, last_event_id=None) -> Iterator[dict]` | GET /agent/stream/{id} (SSE) | -| `schedules` (property) | `-> SchedulerClient` | Cron schedule lifecycle | -| `close` / `close_async` | `() -> None` | Release transport resources | - -## Config and credentials - -`AgentConfig` (dataclass) fields: `server_url="http://localhost:8080/api"`, -`api_key=None`, `auth_key=None`, `auth_secret=None`, `llm_retry_count=3`, -`worker_poll_interval_ms=100`, `worker_thread_count=1`, `auto_start_workers=True`, -`daemon_workers=True`, `auto_register_integrations=False`, -`streaming_enabled=True`, `secret_strict_mode=False`, `log_level="INFO"`. Classmethod -`AgentConfig.from_env()` reads the `AGENTSPAN_*` variables (see [Getting -started](getting-started.md#environment-variables)). Property `api_secret` aliases -`auth_secret`. `AgentRuntime` uses it only for runtime *behaviour* knobs — server -connection always comes from the Conductor `Configuration`. - -`get_secret(name) -> str` — read a credential inside a `@tool(credentials=[...])` -function. `resolve_credentials(task, names) -> dict` — for external workers; reads -the host-resolved values the server delivers on `Task.runtimeMetadata` (declared via -the tool/agent `credentials`, resolved at poll time). Errors: -`CredentialNotFoundError`, `CredentialAuthError`, `CredentialRateLimitError`, -`CredentialServiceError`. - -`ClaudeCode(model_name="", permission_mode=PermissionMode.ACCEPT_EDITS)` with -`PermissionMode` ∈ {`DEFAULT`, `ACCEPT_EDITS`, `PLAN`, `BYPASS`}; `to_model_string()`. - -Skills: `skill(path, model="", agent_models=None, search_path=None, params=None) -> -Agent`; `load_skills(path, model="", agent_models=None) -> dict[str, Agent]`; -`SkillLoadError`. - -Exceptions: `AgentspanError`, `AgentAPIError`, `AgentNotFoundError`, -`ConfigurationError`. +- [API map](reference/api.md) +- [AgentRuntime](reference/runtime.md) +- [AgentClient](reference/client.md) +- [Agent definition fields](reference/agent-definition.md) +- [Agent configuration contract](reference/agent-schema.md) diff --git a/docs/agents/concepts/agents.md b/docs/agents/concepts/agents.md new file mode 100644 index 000000000..ceaf58554 --- /dev/null +++ b/docs/agents/concepts/agents.md @@ -0,0 +1,55 @@ +# Conductor agents + +**Audience:** Python developers authoring durable, LLM-backed Conductor agents. + +## Prerequisites + +Install `conductor-python[agents]`, configure a reachable Conductor server, and +configure the selected provider on that server. Keep provider credentials out of +Python source and workflow input. + +## Define an agent + +Create an `Agent` with a stable name, `provider/model` identifier, instructions, +and optional tools or sub-agents. `@agent` turns a function docstring or return +value into instructions; `Agent.from_instance()` discovers decorated methods on +an object. + +```python +from conductor.ai.agents import Agent, AgentRuntime, tool + +@tool +def get_weather(city: str) -> str: + return f"Weather for {city}" + +agent = Agent(name="weather", model="openai/gpt-4o-mini", + instructions="Answer concisely.", tools=[get_weather]) +with AgentRuntime() as runtime: + print(runtime.run(agent, "Weather in Seattle?").output) +``` + +## Instructions and runtime overrides + +`instructions` may be a string, a callable evaluated during compilation, or a +`PromptTemplate` stored on the server. Use `RunSettings` for one execution's +model, temperature, token, or reasoning override; do not mutate a shared agent +definition per request. An omitted model is valid only for inherited-model or +external-agent designs. + +## Expected result + +`runtime.run()` compiles the agent, starts required local tool workers, and +returns an `AgentResult`. The Conductor UI shows the durable execution and its +tool calls. + +## Common failures + +- A model error normally means the provider credential or model is missing on + the **server**, not merely in the Python process. +- A name that does not match `^[a-zA-Z_][a-zA-Z0-9_-]*$` is rejected. +- A closure or non-importable tool cannot be recovered by a worker process. + +## Next steps + +Use [tools](tools.md) for capabilities, [multi-agent](multi-agent.md) for +composition, and [runtime modes](deploy-serve-run.md) for deployment. diff --git a/docs/agents/concepts/callbacks.md b/docs/agents/concepts/callbacks.md new file mode 100644 index 000000000..59751af30 --- /dev/null +++ b/docs/agents/concepts/callbacks.md @@ -0,0 +1,26 @@ +# Callbacks + +**Audience:** applications that need local observation or lightweight reactions to +Conductor-agent lifecycle events. + +## Prerequisites + +Register callback handlers before starting the runtime. Treat callback payloads as +potentially sensitive execution data. + +`CallbackHandler` observes agent lifecycle events and supports hooks for messages, +tool calls, results, and failures. Keep callback work fast and non-blocking; move +durable business effects into a tool or workflow task. Use structured logging and +redact credentials and sensitive user data. + +## Expected result and failures + +Callbacks receive lifecycle notifications without changing the durable workflow +unless they explicitly fail the local process. Do not use them as the only record +of an audit, notification, or external write: process restarts can interrupt local +observers. + +## Next steps + +Use [streaming](streaming-hitl.md) for caller-visible events and [tools](tools.md) +for durable side effects. diff --git a/docs/agents/concepts/deploy-serve-run.md b/docs/agents/concepts/deploy-serve-run.md new file mode 100644 index 000000000..a45d0f719 --- /dev/null +++ b/docs/agents/concepts/deploy-serve-run.md @@ -0,0 +1,38 @@ +# Deploy · Serve · Run · Plan + +**Audience:** developers choosing a local, CI/CD, or long-lived runtime mode. + +## Prerequisites + +Create an `AgentRuntime` with the target Conductor configuration. Ensure every +Python `@tool` is importable in the process that will call `serve()` or `run()`. + +| Operation | Effect | +|---|---| +| `runtime.plan(agent)` | Compile only; does not register or execute. | +| `runtime.deploy(agent)` | Compile and register; does not execute. | +| `runtime.serve(agent)` | Deploy, start local tool workers, and block. | +| `runtime.run(agent, prompt)` | Start and wait for an `AgentResult`. | +| `runtime.start(agent, prompt)` | Start and return an `AgentHandle`. | +| `runtime.resume(execution_id, agent)` | Reattach workers to a prior execution. | + +Use `deploy` in CI/CD and `serve` in long-lived worker processes. `run` is the +best local quickstart. Always call `shutdown()` or use `with AgentRuntime()` for +short-lived programs. See the [runtime reference](../reference/runtime.md). + +## Production pattern + +Compile/register with `deploy()` during release, then run `serve()` in one or more +long-lived worker services. Use `plan()` in CI to inspect the compiled workflow +before deployment. Use `start()` for asynchronous callers and `resume()` after a +local worker process restart. + +## Expected result and cleanup + +`deploy()` returns deployment information without executing an agent; `serve()` +blocks until interrupted; `run()` returns a terminal `AgentResult`. Use the +runtime context manager or `shutdown()` to stop local polling cleanly. + +## Next steps + +See [deployment and scaling](../../deployment-scaling.md), [runtime reference](../reference/runtime.md), and [agent client](../reference/client.md). diff --git a/docs/agents/concepts/guardrails.md b/docs/agents/concepts/guardrails.md new file mode 100644 index 000000000..82d945d86 --- /dev/null +++ b/docs/agents/concepts/guardrails.md @@ -0,0 +1,36 @@ +# Guardrails + +**Audience:** authors defining validation and safety controls for Conductor-agent +input, output, or tool work. + +## Prerequisites + +Decide whether failure should block, retry, repair, or require human review. Test +both a passing and failing value before deploying a guardrail. + +Guardrails validate input or output before it affects the next agent step. Use +`RegexGuardrail`, `LLMGuardrail`, a `Guardrail`, or `@guardrail` for custom logic. +`OnFail` can retry, raise, fix, or request a human decision. + +Attach guardrails to an agent or tool, keep custom functions importable by worker +processes, and avoid sending secrets to model-based validation. Test both pass and +failure paths. See [API reference](../reference/api.md). + +## Patterns + +Use `RegexGuardrail` for deterministic format checks, `LLMGuardrail` for semantic +policy checks, and a custom `@guardrail` only when the rule needs application +state. Apply a tool guardrail closest to the side effect; use an agent guardrail +for broad input/output policy. A retry policy is appropriate only when a new model +response can plausibly pass. + +## Expected result and failures + +Guardrail decisions appear in the execution history. If a model-based guardrail +receives a secret or raw sensitive record, remove that input and validate a +redacted representation instead. + +## Next steps + +Pair guardrails with [human approval](streaming-hitl.md), [tool policy](tools.md), +and [security guidance](../../security.md). diff --git a/docs/agents/concepts/multi-agent.md b/docs/agents/concepts/multi-agent.md new file mode 100644 index 000000000..43e6b8a6e --- /dev/null +++ b/docs/agents/concepts/multi-agent.md @@ -0,0 +1,41 @@ +# Multi-agent systems + +**Audience:** authors composing specialists into a durable Conductor-agent graph. + +## Prerequisites + +Each participating agent needs a unique stable name and bounded execution policy. +Start with a single agent and a tested tool before introducing delegation. + +`Agent(agents=[...], strategy=...)` supports `SEQUENTIAL`, `PARALLEL`, `HANDOFF`, +`ROUTER`, `ROUND_ROBIN`, `RANDOM`, `SWARM`, `MANUAL`, and `PLAN_EXECUTE`. + +- Sequential and parallel strategies compose deterministic work. +- Handoff, router, and swarm transfer control between agents; restrict transitions + and define an empty-safe fallback. +- `PLAN_EXECUTE` compiles a typed `Plan` into a durable sub-workflow and can replan. +- `agent_tool(child)` calls a child agent as a tool instead of transferring control. + +Set a termination condition and a turn/token limit for every open-ended design. +See [termination](termination.md), [plans](../reference/agent-definition.md), and +[writing examples](../../../examples/agents/README.md). + +## Handoffs, routers, and plans + +Use `HANDOFF` when the model should select a specialist, `ROUTER` when an explicit +router chooses one, and `agent_tool(child)` when a parent needs a child's result +without transferring control. `PLAN_EXECUTE` is for typed, inspectable plans that +must survive restarts; provide a planner and an optional fallback/replan policy. +Restrict allowed transitions so an unexpected model output cannot reach an unsafe +specialist. + +## Expected result and failures + +The compiled parent contains durable child/sub-workflow work and each child is +visible in execution history. If a graph loops, add `max_turns`, termination, and +a bounded planner/replan policy before retrying. + +## Next steps + +Read [termination](termination.md), [stateful agents](stateful.md), and the +[agent definition reference](../reference/agent-definition.md). diff --git a/docs/agents/concepts/scheduling.md b/docs/agents/concepts/scheduling.md new file mode 100644 index 000000000..93cbbb274 --- /dev/null +++ b/docs/agents/concepts/scheduling.md @@ -0,0 +1,24 @@ +# Scheduling + +**Audience:** operators scheduling recurring Conductor-agent executions. + +## Prerequisites + +Deploy the agent, choose a stable schedule name, and decide the correlation or +idempotency key that prevents duplicate business work. + +Deploy an agent before scheduling it, then use the shared scheduler client from +`runtime.schedules_client()` or `OrkesClients.get_scheduler_client()`. Use stable +schedule names, timezone-aware cron expressions, and idempotent workflow input. + +The schedule API is the core workflow scheduler, not a separate agent-only facade. +See [SCHEDULE.md](../../SCHEDULE.md) for request fields and lifecycle operations. + +## Expected result and cleanup + +A saved schedule starts the deployed agent on its cron cadence. Pause or delete +the schedule before removing the agent definition or retiring the worker service. + +## Next steps + +Read [schedules and events](../../schedules-events.md) and [runtime modes](deploy-serve-run.md). diff --git a/docs/agents/concepts/stateful.md b/docs/agents/concepts/stateful.md new file mode 100644 index 000000000..a59e3c183 --- /dev/null +++ b/docs/agents/concepts/stateful.md @@ -0,0 +1,27 @@ +# Stateful agents + +**Audience:** applications that need durable, session-scoped conversation state. + +## Prerequisites + +Choose a stable `session_id`, define retention/deletion policy, and classify which +user data may be persisted before enabling state. + +Set `stateful=True` when a session needs durable conversation state and worker +isolation. Pass a stable session identifier when resuming a conversation, use +`ConversationMemory` or `SemanticMemory` only for data appropriate to persist, +and define retention rules for user data. + +Stateful runs use liveness monitoring by default. Configure it with +`CONDUCTOR_AGENT_LIVENESS_*` and use `resume()` after a process restart. + +## Expected result and failures + +Runs with the same session identifier can resume the intended durable state. +Unexpected context growth should be handled with retention, memory limits, or +context condensation—not by placing unbounded history in prompts. + +## Next steps + +Read [streaming and approval](streaming-hitl.md), [security](../../security.md), +and [runtime modes](deploy-serve-run.md). diff --git a/docs/agents/concepts/streaming-hitl.md b/docs/agents/concepts/streaming-hitl.md new file mode 100644 index 000000000..761defbf8 --- /dev/null +++ b/docs/agents/concepts/streaming-hitl.md @@ -0,0 +1,34 @@ +# Streaming and human-in-the-loop + +**Audience:** interactive applications that display progress or require a human +decision before a tool executes. + +## Prerequisites + +Keep the execution ID returned by the runtime or handle. Define who may approve, +reject, or respond and validate every human-supplied value. + +`runtime.stream()` yields `AgentEvent` values while an execution runs; when SSE is +unavailable, the runtime can fall back to status polling. Use `human_tool` or a +wait tool to pause work, then approve, reject, or respond through `AgentHandle`. + +Do not treat streamed content as a final result until the terminal event arrives. +Use event execution IDs when approving nested or sub-agent work. + +## Approval pattern + +Use `human_tool` or a wait-for-message tool for an explicit durable pause. Resume +through the handle/client control plane rather than relying on an in-memory web +request. Make approval actions idempotent because callers may retry a network +request. + +## Expected result and cleanup + +The stream yields progress followed by a terminal event, while an approval task +remains visible as waiting work in Conductor. Close stream consumers and stop +short-lived runtimes after the terminal event. + +## Next steps + +See [tools](tools.md), [agent client](../reference/client.md), and +[callbacks](callbacks.md). diff --git a/docs/agents/concepts/structured-output.md b/docs/agents/concepts/structured-output.md new file mode 100644 index 000000000..747c341bf --- /dev/null +++ b/docs/agents/concepts/structured-output.md @@ -0,0 +1,26 @@ +# Structured output + +**Audience:** callers requiring a validated, typed final answer from a +Conductor agent. + +## Prerequisites + +Define a small dataclass or Pydantic model with explicit optional fields. Ensure +the prompt asks for the same shape the model declares. + +Set `output_type` to a dataclass or Pydantic model to ask the server to validate +the final result against a schema. Keep the model small, make optional fields +explicit, and handle validation failures as retryable only when the prompt can +produce a different result. The returned `AgentResult.output` is the parsed value +when validation succeeds. + +## Expected result and failures + +Successful runs return the parsed typed value. A validation error means the model +did not satisfy the requested contract; improve instructions or use a bounded +retry policy rather than silently accepting malformed data. + +## Next steps + +See [agent schema](../reference/agent-schema.md), [guardrails](guardrails.md), and +[runtime reference](../reference/runtime.md). diff --git a/docs/agents/concepts/termination.md b/docs/agents/concepts/termination.md new file mode 100644 index 000000000..669cd7e35 --- /dev/null +++ b/docs/agents/concepts/termination.md @@ -0,0 +1,26 @@ +# Termination conditions + +**Audience:** authors bounding cost, time, and unsafe open-ended delegation. + +## Prerequisites + +Set a meaningful `max_turns` and decide which completion signal is safe for the +business operation before selecting an additional termination condition. + +Use `MaxMessageTermination`, `StopMessageTermination`, `TextMentionTermination`, +or `TokenUsageTermination` to bound agent execution. Conditions compose with `&` +and `|`; pair them with `max_turns` for defense in depth. + +Stop a live execution through `AgentHandle` or `AgentClient.stop()`. A stop should +be safe to repeat and must not assume an in-flight external tool call is reversible. + +## Expected result and failures + +A terminal condition ends the durable execution with its recorded reason. If a +tool has already started, stopping the agent does not undo its external side +effect; design compensating work where necessary. + +## Next steps + +Continue with [multi-agent](multi-agent.md), [reliability](../../reliability.md), +and [agent client control](../reference/client.md). diff --git a/docs/agents/concepts/tools.md b/docs/agents/concepts/tools.md new file mode 100644 index 000000000..b68d595fa --- /dev/null +++ b/docs/agents/concepts/tools.md @@ -0,0 +1,62 @@ +# Tools + +**Audience:** authors exposing Python or server-native capabilities to a +Conductor agent. + +## Prerequisites + +Install `conductor-python[agents]`. Tool functions must be importable by worker +processes and safe to receive more than once. + +## Define a Python tool + +`@tool` converts Python type hints and docstrings into a tool schema. Each call is +a durable, retryable Conductor task. + +```python +from conductor.ai.agents import ToolContext, tool + +@tool(credentials=["GITHUB_TOKEN"]) +def create_issue(title: str, context: ToolContext) -> str: + token = context.get_credential("GITHUB_TOKEN") + return f"created: {title}" +``` + +## Choose the right tool + +| Need | Factory or pattern | +|---|---| +| Python business logic | `@tool` | +| HTTP endpoint | `http_tool` | +| OpenAPI/Postman discovery | `api_tool` | +| MCP server | `mcp_tool` | +| Human decision | `human_tool` | +| PDF, media, or vector retrieval | built-in PDF/media/index/search factories | +| Another Conductor agent | `agent_tool` | + +The built-in factories compile to Conductor system tasks where possible; prefer +them to hand-written wrapper workers. Declare credentials on the tool or agent so +the server resolves them into task runtime metadata. Do not read credentials from +ambient environment variables or store them in workflow input. + +Use command/code tools only with an allowlist. See [security](../../security.md) +and the complete Python signatures in [API reference](../reference/api.md). + +## Reliability and approval + +Use `retry_count`, `retry_delay_seconds`, `timeout_seconds`, and an idempotency +key appropriate to the external system. Mark destructive operations with +`approval_required=True` or model them with `human_tool`. A tool may accept +`ToolContext` for execution ID, session state, and resolved credentials. + +## Expected result and failures + +A successful tool appears as a named task in the agent execution. A task that +remains `SCHEDULED` has no compatible worker polling; a failed credential lookup +must be fixed in the server credential store rather than by adding a secret to +the prompt. + +## Next steps + +Continue with [guardrails](guardrails.md), [streaming and approval](streaming-hitl.md), +or [security](../../security.md). diff --git a/docs/agents/framework-agents.md b/docs/agents/framework-agents.md index 53e060849..563082b58 100644 --- a/docs/agents/framework-agents.md +++ b/docs/agents/framework-agents.md @@ -1,160 +1,9 @@ -# Framework agents +# Conductor-agent framework bridges -Conductor Agent can run agents authored in other frameworks by bridging them onto its -durable runtime. You keep your framework's authoring API; Conductor Agent handles -durability, retries, streaming, and observability. +This compatibility page has moved to individual framework guides: -Supported bridges: **OpenAI Agents SDK**, **LangChain**, **LangGraph**, **Claude -Agent SDK**. The runtime auto-detects the framework from the object you pass to -`runtime.run(...)`. - -- [OpenAI Agents SDK](#openai-agents-sdk) -- [LangChain](#langchain) -- [LangGraph](#langgraph) -- [Claude Agent SDK](#claude-agent-sdk) - -## OpenAI Agents SDK - -Two ways. Either keep your existing `agents.Agent` and swap the runner, or use the -SDK's `Runner` with a native `Agent`. - -### Drop-in `Runner` - -Change one import — `from conductor.ai import Runner` instead of `from agents import -Runner` — and run your existing OpenAI-Agents agent on Conductor Agent: - -```python -from conductor.ai import Runner # the one line that changes -from agents import Agent, function_tool - -@function_tool -def get_weather(city: str) -> str: - return f"72F and sunny in {city}" - -agent = Agent( - name="weather_assistant", - model="gpt-4o", - tools=[get_weather], - instructions="You are a helpful assistant.", -) - -result = Runner.run_sync(agent, "What's the weather in NYC?") -print(result.final_output) -``` - -`Runner` methods (all classmethods, accept an OpenAI-Agents `Agent` or a native -`Agent`): - -- `Runner.run_sync(starting_agent, input, *, context=None, max_turns=10, **kwargs) -> RunResult` -- `await Runner.run(starting_agent, input, *, context=None, max_turns=10, **kwargs) -> RunResult` -- `await Runner.run_streamed(starting_agent, input, *, context=None, max_turns=10, **kwargs) -> AsyncAgentStream` - -`RunResult` exposes `.final_output` and `.execution_id`. (`context` is accepted for -compatibility and ignored.) - -```python -import asyncio -from conductor.ai import Runner -from agents import Agent - -agent = Agent(name="Assistant", instructions="You only respond in haikus.") -result = asyncio.run(Runner.run(agent, "Tell me about recursion.")) -print(result.final_output) -``` - -`from conductor.ai import function_tool` is an alias of `@tool` for source compatibility. - -## LangChain - -Build a LangChain agent, then hand it to `runtime.run(...)`: - -```python -from conductor.ai.agents import AgentRuntime -from langchain.agents import create_agent -from langchain_core.tools import tool as lc_tool - -@lc_tool -def check_token() -> str: - """Check a token.""" - return "available" - -agent = create_agent("openai:gpt-4o", tools=[check_token], - system_prompt="You are a helpful assistant.") - -with AgentRuntime() as runtime: - result = runtime.run(agent, "Is the token set?", credentials=["GITHUB_TOKEN"]) - result.print_result() -``` - -Conductor Agent also provides a thin wrapper, `conductor.ai.agents.langchain.create_agent`, -that captures the model, tools, and system prompt up front so they compile to native -server-side model + tool tasks (rather than running the whole agent in one opaque -worker). - -## LangGraph - -Pass a compiled graph (e.g. from `create_react_agent` or your own -`StateGraph().compile()`) to `runtime.run(...)`: - -```python -import math -from langchain_core.tools import tool -from langchain_openai import ChatOpenAI -from langgraph.prebuilt import create_react_agent -from conductor.ai.agents import AgentRuntime - -@tool -def calculate(expression: str) -> str: - """Evaluate a math expression.""" - return str(eval(expression, {"__builtins__": {}}, {"sqrt": math.sqrt, "pi": math.pi})) - -llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) -graph = create_react_agent(llm, tools=[calculate], name="math_agent") - -with AgentRuntime() as runtime: - result = runtime.run(graph, "What is sqrt(256) + 2**10?") - result.print_result() -``` - -The bridge tries, in order, full extraction (model + `ToolNode` tools), then a -graph-structure compilation (nodes/edges become tasks), then passthrough. To mark a -node as requiring human input, decorate it with `human_task`: - -```python -from conductor.ai.agents.frameworks.langgraph import human_task - -@human_task(prompt="Review and approve before continuing.") -def approval_node(state): ... -``` - -## Claude Agent SDK - -Run a Claude Agent SDK / Claude Code agent. The simplest path is a native `Agent` -configured with `ClaudeCode`: - -```python -from conductor.ai.agents import Agent, AgentRuntime, ClaudeCode - -fixer = Agent( - name="claude_code_fixer", - model=ClaudeCode("sonnet", - permission_mode=ClaudeCode.PermissionMode.ACCEPT_EDITS), - credentials=["GITHUB_TOKEN"], - instructions="You are a senior developer fixing a GitHub issue.", - tools=["Bash", "Read", "Write", "Edit", "Glob", "Grep"], # built-in string tools only - max_turns=50, -) - -with AgentRuntime() as rt: - result = rt.run(fixer, "Pick an open issue and open a PR.", timeout=600000) - result.print_result() -``` - -`ClaudeCode(model_name="", permission_mode=PermissionMode.ACCEPT_EDITS)`. -`permission_mode` is one of `DEFAULT`, `ACCEPT_EDITS`, `PLAN`, `BYPASS`. Claude Code -agents support the built-in string tools (`Read`, `Edit`, `Bash`, ...); custom `@tool` -functions are not yet supported there. - -You can also bring `ClaudeCodeOptions` / a Claude Agent SDK agent directly; the bridge -runs the full `query()` in one durable worker with instrumentation hooks that stream -tool-use and lifecycle events back to Conductor Agent. +- [Google ADK](frameworks/google-adk.md) +- [LangChain](frameworks/langchain.md) +- [LangGraph](frameworks/langgraph.md) +- [OpenAI Agents SDK](frameworks/openai.md) +- [Claude Agent SDK](frameworks/claude-agent-sdk.md) diff --git a/docs/agents/frameworks/claude-agent-sdk.md b/docs/agents/frameworks/claude-agent-sdk.md new file mode 100644 index 000000000..1c94f684a --- /dev/null +++ b/docs/agents/frameworks/claude-agent-sdk.md @@ -0,0 +1,14 @@ +# Claude Agent SDK + +**Prerequisites:** install `conductor-python[claude]`, configure the provider on +the Conductor server, and review any repository or shell access before use. + +Install `conductor-python[claude]` and the supported Claude Agent SDK dependency. +Pass a native Claude agent/options object to `AgentRuntime`; the bridge adapts it +to a durable Conductor-agent execution and preserves tool lifecycle events. + +Review CLI/code tool allowlists before running against real repositories or +credentials. See [tools](../concepts/tools.md) and the framework examples. + +**Expected result:** the native Claude agent executes through the durable runtime +while preserving tool lifecycle events. **Next:** read [security](../../security.md). diff --git a/docs/agents/frameworks/google-adk.md b/docs/agents/frameworks/google-adk.md new file mode 100644 index 000000000..65828cb17 --- /dev/null +++ b/docs/agents/frameworks/google-adk.md @@ -0,0 +1,30 @@ +# Google ADK + +**Prerequisites:** install the `adk` extra and configure the selected provider on +the Conductor server. + +Install the optional dependency: + +```shell +pip install 'conductor-python[adk]' +``` + +Pass a standard Google ADK `Agent` to `AgentRuntime.run()`. The runtime detects +the framework object, serializes its instructions, tools, and sub-agent graph, +and starts a durable Conductor-agent execution. + +```python +from conductor.ai.agents import AgentRuntime +from google.adk.agents import Agent + +agent = Agent(name="adk_greeter", model="gemini-2.0-flash", + instruction="Be concise.") +with AgentRuntime() as runtime: + print(runtime.run(agent, "Say hello.").output) +``` + +See [runnable ADK examples](../../../examples/agents/adk/README.md). Provider +credentials must be configured on the Conductor server. + +**Expected result:** the native ADK agent runs as a durable Conductor-agent +execution. **Next:** review [tools](../concepts/tools.md) and [runtime modes](../concepts/deploy-serve-run.md). diff --git a/docs/agents/frameworks/langchain.md b/docs/agents/frameworks/langchain.md new file mode 100644 index 000000000..2ce19dab0 --- /dev/null +++ b/docs/agents/frameworks/langchain.md @@ -0,0 +1,14 @@ +# LangChain + +**Prerequisites:** install `conductor-python[langchain]`; keep callback and tool +functions importable by worker processes. + +Install `conductor-python[langchain]`, then pass supported LangChain agents or +tools to the runtime bridge. The bridge retains framework authoring while +Conductor supplies durable execution, worker-backed tools, and observability. + +Keep callbacks and tool callables importable by worker processes. See the +[framework examples](../../../examples/agents/README.md) and [tools](../concepts/tools.md). + +**Expected result:** the bridge preserves LangChain authoring while Conductor owns +durable execution. **Next:** use [runtime modes](../concepts/deploy-serve-run.md). diff --git a/docs/agents/frameworks/langgraph.md b/docs/agents/frameworks/langgraph.md new file mode 100644 index 000000000..e7689cfbf --- /dev/null +++ b/docs/agents/frameworks/langgraph.md @@ -0,0 +1,14 @@ +# LangGraph + +**Prerequisites:** install `conductor-python[langgraph]` and keep recoverable +graph nodes free of process-local state. + +Install `conductor-python[langgraph]` and pass a supported LangGraph graph to +`AgentRuntime`. The bridge executes graph work through the durable runtime and +preserves streaming and human-task integration where the graph exposes it. + +Start with the [LangGraph examples](../../../examples/agents/langgraph/README.md). +Avoid closures and process-local state in nodes that must run after recovery. + +**Expected result:** graph work and emitted events are recorded in a durable +Conductor-agent execution. **Next:** read [streaming](../concepts/streaming-hitl.md). diff --git a/docs/agents/frameworks/openai.md b/docs/agents/frameworks/openai.md new file mode 100644 index 000000000..8006e24c6 --- /dev/null +++ b/docs/agents/frameworks/openai.md @@ -0,0 +1,19 @@ +# OpenAI Agents SDK + +**Prerequisites:** install `conductor-python[openai-agents]` and configure the +provider credential on the Conductor server. + +Install `conductor-python[openai-agents]`. The top-level `Runner` and +`function_tool` compatibility surface lets OpenAI Agents SDK-style applications +run through Conductor while retaining familiar agent, tool, handoff, and streaming +shapes. + +```python +from conductor.ai import Runner, function_tool +``` + +The runtime still requires a Conductor server and server-side provider credential. +See the [OpenAI examples](../../../examples/agents/openai/README.md). + +**Expected result:** familiar OpenAI Agents-style calls execute through Conductor. +**Next:** see [tools](../concepts/tools.md) and [runtime modes](../concepts/deploy-serve-run.md). diff --git a/docs/agents/getting-started.md b/docs/agents/getting-started.md index b9518efae..52f011554 100644 --- a/docs/agents/getting-started.md +++ b/docs/agents/getting-started.md @@ -1,84 +1,42 @@ -# Getting started +# Run your first durable Conductor agent -## Under 30 seconds +**Prerequisites:** Python 3.10+, a reachable Conductor server, and an LLM provider +credential configured on that server. -The agents API ships as an extra of `conductor-python` (see `pyproject.toml`). +## 1. Configure the client -```bash +```shell pip install 'conductor-python[agents]' +export CONDUCTOR_SERVER_URL=http://localhost:8080/api +export CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini ``` -Or, per framework, install just what you need — e.g. `conductor-python[langchain]`, -`conductor-python[adk]`, `conductor-python[claude]`. +For authenticated servers, also set `CONDUCTOR_AUTH_KEY` and +`CONDUCTOR_AUTH_SECRET`. Do not export provider secrets into application source; +configure the provider integration on the server. -Point the SDK at a running Conductor Agent server (defaults to `http://localhost:8080/api`): +## 2. Run the maintained example -```bash -export AGENTSPAN_SERVER_URL=http://localhost:8080/api -export OPENAI_API_KEY= -export AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini +```shell +cd examples/agents +python 01_basic_agent.py ``` -Write `hello.py`: +Expected result: an `AgentResult` with the model response. If the request cannot +reach the server, check [server setup](../server-setup.md). If the agent cannot +call a model, check the server-side provider integration. + +## 3. Create the same agent ```python from conductor.ai.agents import Agent, AgentRuntime -agent = Agent( - name="greeter", - model="anthropic/claude-sonnet-4-6", - instructions="You are a friendly assistant. Keep responses brief.", -) - +agent = Agent(name="greeter", model="openai/gpt-4o-mini", + instructions="You are a friendly assistant.") with AgentRuntime() as runtime: - result = runtime.run(agent, "Say hello and tell me a fun fact about Python.") + result = runtime.run(agent, "Say hello.") print(result.output) ``` -Run it: - -```bash -uv run python hello.py -``` - -That is the whole loop: define an `Agent`, open an `AgentRuntime`, call `run`. The -runtime compiles the agent to a workflow, starts it, and blocks until it returns an -[`AgentResult`](api-reference.md#agentresult). `result.print_result()` pretty-prints -the output if you prefer. - -## Environment variables - -`AgentConfig.from_env()` reads these (all optional — defaults shown): - -| Variable | Default | Purpose | -|---|---|---| -| `AGENTSPAN_SERVER_URL` | `http://localhost:8080/api` | Server base URL | -| `AGENTSPAN_API_KEY` | — | API key auth | -| `AGENTSPAN_AUTH_KEY` | — | Key/secret auth — key | -| `AGENTSPAN_AUTH_SECRET` | — | Key/secret auth — secret | -| `AGENTSPAN_LLM_RETRY_COUNT` | `3` | LLM call retries | -| `AGENTSPAN_WORKER_POLL_INTERVAL` | `100` | Worker poll interval (ms) | -| `AGENTSPAN_WORKER_THREADS` | `1` | Worker thread count | -| `AGENTSPAN_AUTO_START_WORKERS` | `true` | Auto-start local tool workers | -| `AGENTSPAN_DAEMON_WORKERS` | `true` | Run workers as daemon threads | -| `AGENTSPAN_INTEGRATIONS_AUTO_REGISTER` | `false` | Auto-register provider integrations | -| `AGENTSPAN_STREAMING_ENABLED` | `true` | Enable SSE streaming | -| `AGENTSPAN_SECRET_STRICT_MODE` | `false` | Fail hard on missing secrets | -| `AGENTSPAN_LOG_LEVEL` | `INFO` | Log level | - -The model string is `"provider/model"`, e.g. `anthropic/claude-sonnet-4-6`, -`anthropic/claude-sonnet-4-20250514`, `google_gemini/gemini-2.0-flash`. Set the -matching provider API key in the environment of whoever runs the agent's workers. - -## What `model` looks like - -```python -Agent(name="a", model="openai/gpt-4o") # OpenAI -Agent(name="b", model="anthropic/claude-sonnet-4-20250514") -Agent(name="c", model="google_gemini/gemini-2.0-flash") -``` - -## Next - -- Add tools, sub-agents, and human-in-the-loop: [Writing agents](writing-agents.md). -- Deploy once and serve workers separately for production: [Advanced](advanced.md). +Next: [tools](concepts/tools.md), [runtime modes](concepts/deploy-serve-run.md), +or [framework bridges](README.md#framework-bridges). diff --git a/docs/agents/reference/agent-definition.md b/docs/agents/reference/agent-definition.md new file mode 100644 index 000000000..fe630aa93 --- /dev/null +++ b/docs/agents/reference/agent-definition.md @@ -0,0 +1,12 @@ +# Agent definition fields + +`Agent` accepts a name, `provider/model`, instructions, tools, sub-agents, and +runtime policy. Important fields include `strategy`, `max_turns`, `max_tokens`, +`temperature`, `timeout_seconds`, `output_type`, `guardrails`, `termination`, +`handoffs`, `credentials`, `stateful`, `enable_planning`, `callbacks`, and +`fallback`. + +Names must match `^[a-zA-Z_][a-zA-Z0-9_-]*$`. Empty models represent inherited or +external-agent behavior. The complete constructor and serialization semantics are +maintained in [api-reference.md](../api-reference.md) and +`AgentConfigSerializer`; use those sources when adding a newly supported field. diff --git a/docs/agents/reference/agent-schema.json b/docs/agents/reference/agent-schema.json new file mode 100644 index 000000000..12656b475 --- /dev/null +++ b/docs/agents/reference/agent-schema.json @@ -0,0 +1,87 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/conductor-oss/python-sdk/blob/main/docs/agents/reference/agent-schema.json", + "title": "Conductor Python AgentConfig", + "description": "The public wire shape emitted by AgentConfigSerializer under agentConfig.", + "$ref": "#/$defs/agentConfig", + "$defs": { + "jsonValue": { + "anyOf": [ + { "type": "string" }, { "type": "number" }, { "type": "integer" }, + { "type": "boolean" }, { "type": "null" }, + { "type": "array", "items": { "$ref": "#/$defs/jsonValue" } }, + { "type": "object", "additionalProperties": { "$ref": "#/$defs/jsonValue" } } + ] + }, + "taskReference": { + "type": "object", + "required": ["taskName"], + "properties": { "taskName": { "type": "string", "minLength": 1 } }, + "additionalProperties": false + }, + "tool": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "description": { "type": "string" }, + "toolType": { "type": "string" }, + "taskName": { "type": "string" }, + "inputSchema": { "type": "object" }, + "outputSchema": { "type": "object" }, + "credentials": { "type": "array", "items": { "type": "string" } }, + "approvalRequired": { "type": "boolean" } + }, + "additionalProperties": true + }, + "agentConfig": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": "string", "pattern": "^[a-zA-Z_][a-zA-Z0-9_-]*$" }, + "model": { "type": ["string", "null"] }, + "baseUrl": { "type": ["string", "null"] }, + "strategy": { "type": ["string", "null"] }, + "maxTurns": { "type": ["integer", "null"], "minimum": 0 }, + "timeoutSeconds": { "type": ["integer", "null"], "minimum": 0 }, + "external": { "type": ["boolean", "null"] }, + "instructions": { "anyOf": [{ "type": "string" }, { "type": "object" }, { "type": "null" }] }, + "tools": { "type": "array", "items": { "$ref": "#/$defs/tool" } }, + "agents": { "type": "array", "items": { "$ref": "#/$defs/agentConfig" } }, + "router": { "anyOf": [{ "$ref": "#/$defs/taskReference" }, { "$ref": "#/$defs/agentConfig" }] }, + "outputType": { "type": "object" }, + "guardrails": { "type": "array", "items": { "type": "object" } }, + "memory": { "type": "object" }, + "maxTokens": { "type": "integer", "minimum": 0 }, + "contextWindowBudget": { "type": "integer", "minimum": 0 }, + "temperature": { "type": "number" }, + "reasoningEffort": { "type": "string" }, + "stopWhen": { "$ref": "#/$defs/taskReference" }, + "termination": { "type": "object" }, + "handoffs": { "type": "array", "items": { "type": "object" } }, + "allowedTransitions": { "type": "array", "items": { "type": "string" } }, + "introduction": { "type": "string" }, + "metadata": { "type": "object" }, + "enablePlanning": { "type": "boolean" }, + "planner": { "$ref": "#/$defs/agentConfig" }, + "fallback": { "$ref": "#/$defs/agentConfig" }, + "callbacks": { "type": "array", "items": { "type": "object" } }, + "includeContents": { "type": "boolean" }, + "thinkingConfig": { "type": "object" }, + "requiredTools": { "type": "array", "items": { "type": "string" } }, + "prefillTools": { "type": "array", "items": { "type": "object" } }, + "fallbackMaxTurns": { "type": "integer", "minimum": 0 }, + "planSource": { "type": "string" }, + "plannerContext": { "type": "array", "items": { "type": "object" } }, + "synthesize": { "type": "boolean" }, + "maskedFields": { "type": "array", "items": { "type": "string" } }, + "gate": { "type": "object" }, + "codeExecution": { "type": "object" }, + "cliConfig": { "type": "object" }, + "credentials": { "type": "array", "items": { "type": "string" } }, + "_framework": { "type": "string" } + }, + "additionalProperties": false + } + } +} diff --git a/docs/agents/reference/agent-schema.md b/docs/agents/reference/agent-schema.md new file mode 100644 index 000000000..135cb019d --- /dev/null +++ b/docs/agents/reference/agent-schema.md @@ -0,0 +1,30 @@ +# Agent configuration contract + +**Audience:** integrators inspecting or validating the `agentConfig` payload sent +to the Conductor agent control plane. + +## Canonical schema + +The canonical Python wire contract is the dictionary emitted by +`conductor.ai.agents.config_serializer.AgentConfigSerializer` under the +`agentConfig` request field. Its serializer tests validate supported agent, +tool, guardrail, handoff, memory, termination, callbacks, planning, and framework +configuration shapes. + +Configuration keys are camelCase on the wire even when Python constructor fields +are snake_case. Nested `agents`, `router`, `planner`, and `fallback` serialize +recursively. The published [JSON Schema](agent-schema.json) rejects unknown root +agent fields while allowing intentionally open JSON payloads such as metadata, +tool input schemas, and framework passthrough configuration. + +## Compatibility + +The schema describes payloads emitted by the current Python serializer; it is not +a promise that arbitrary server-side fields can be supplied by callers. Extend the +serializer, schema, reference, and contract tests together when adding a public +agent field. For request envelopes and responses, see [AgentClient](client.md). + +## Next steps + +Read [agent definition fields](agent-definition.md) for Python authoring and +[runtime](runtime.md) for submission lifecycle. diff --git a/docs/agents/reference/api.md b/docs/agents/reference/api.md new file mode 100644 index 000000000..c6ba110c3 --- /dev/null +++ b/docs/agents/reference/api.md @@ -0,0 +1,14 @@ +# Agent API map + +| Need | Python API | Guide | +|---|---|---| +| Define an agent | `Agent`, `@agent`, `Agent.from_instance` | [Agents](../concepts/agents.md) | +| Define tools | `@tool`, `ToolDef`, built-in tool factories | [Tools](../concepts/tools.md) | +| Run lifecycle operations | `AgentRuntime`, module-level helpers | [Runtime](runtime.md) | +| Control an execution | `AgentClient`, `AgentHandle` | [Control plane](client.md) | +| Add safety controls | guardrails and termination classes | [Guardrails](../concepts/guardrails.md) | +| Compose agents | `Strategy`, handoffs, `plan_execute` | [Multi-agent](../concepts/multi-agent.md) | +| Bridge frameworks | framework extras | [Framework bridges](../README.md) | + +All public agent imports are available from `conductor.ai.agents`; detailed legacy +API lists remain available through [api-reference.md](../api-reference.md). diff --git a/docs/agents/reference/client.md b/docs/agents/reference/client.md new file mode 100644 index 000000000..eedbeab3b --- /dev/null +++ b/docs/agents/reference/client.md @@ -0,0 +1,29 @@ +# AgentClient control-plane reference + +**Audience:** applications that need direct control-plane access without managing +local Python tool workers. + +## Prerequisites + +Use a configured SDK client and ensure tools are server-native or already served +by a deployed worker process. + +`AgentClient` is the typed transport interface for `/agent/*` endpoints. Its +`OrkesAgentClient` implementation shares the SDK transport, token refresh, and +authentication behavior. + +| Operation | Method | +|---|---| +| Compile, deploy, start | `compile_agent`, `deploy_agent`, `start_agent` | +| Inspect | `get_status`, `get_execution`, `list_executions` | +| Human/control actions | `respond`, `stop`, `signal` | +| Stream events | `stream_sse` / `stream_sse_async` | + +Every operation has an async counterpart. `SSEUnavailableError` lets callers +choose a status-polling fallback. Use `AgentRuntime` unless an application needs +direct control-plane integration. + +## Expected result and next steps + +`stop`, `signal`, and `respond` change a durable execution; authorize callers and +make externally triggered requests idempotent. See [streaming and approval](../concepts/streaming-hitl.md) and [runtime](runtime.md). diff --git a/docs/agents/reference/runtime.md b/docs/agents/reference/runtime.md new file mode 100644 index 000000000..67eeb6ed1 --- /dev/null +++ b/docs/agents/reference/runtime.md @@ -0,0 +1,38 @@ +# AgentRuntime reference + +**Audience:** applications owning local tool workers and Conductor-agent lifecycle. + +## Prerequisites + +Create one runtime per application lifetime with a configured server connection. +Use a context manager for scripts and an application shutdown hook for services. + +`AgentRuntime` owns connection-facing clients, local tool workers, and execution +lifecycle. Use it as a context manager or call `shutdown()`. + +| Method | Returns | Purpose | +|---|---|---| +| `run` / `run_async` | `AgentResult` | Start and wait for completion. | +| `start` / `start_async` | `AgentHandle` | Start without waiting. | +| `stream` / `stream_async` | stream | Consume agent events. | +| `plan` | compiled definition | Compile without registration or execution. | +| `deploy` / `deploy_async` | deployment information | Register agent definitions. | +| `serve` | none | Deploy and poll tool workers. | +| `resume` / `resume_async` | `AgentHandle` | Reattach to an execution. | + +`AgentConfig.from_env()` reads canonical `CONDUCTOR_AGENT_*` settings. Connection, +auth, and SDK log level belong to `Configuration` and use `CONDUCTOR_*`. Share one +runtime for an application lifetime; do not create one per request. + +## Expected result and common failures + +`RunSettings` overrides model/runtime options for one run without mutating the +agent definition. Use `on_event` or `stream()` for progress, and `resume()` to +reattach local workers after a process restart. A `SCHEDULED` tool task means no +compatible worker process is polling; a model error normally means the server +provider setup is incomplete. + +## Cleanup and next steps + +Call `shutdown()` or exit the context manager to stop local workers. Continue with +[runtime modes](../concepts/deploy-serve-run.md) and [client control](client.md). diff --git a/docs/agents/writing-agents.md b/docs/agents/writing-agents.md index 057e9c6e0..2624753ef 100644 --- a/docs/agents/writing-agents.md +++ b/docs/agents/writing-agents.md @@ -1,479 +1,8 @@ -# Writing agents +# Writing Conductor agents -Everything is an `Agent`. A single agent wraps an LLM plus tools. An agent with -sub-agents is a multi-agent system. Compose, then run with an -[`AgentRuntime`](advanced.md). +This compatibility page has moved to the task-oriented guides: -- [Defining an agent](#defining-an-agent) -- [Instructions (static, dynamic, templated)](#instructions) -- [Tools](#tools) -- [Built-in tools](#built-in-tools) -- [Multi-agent strategies](#multi-agent-strategies) -- [Handoffs (swarm)](#handoffs-swarm) -- [Guardrails](#guardrails) -- [Termination and TextGate](#termination-and-textgate) -- [Callbacks](#callbacks) -- [Streaming and human-in-the-loop](#streaming-and-human-in-the-loop) -- [Schedules](#schedules) -- [Agents from a class (`Agent.from_instance`)](#agents-from-a-class) -- [Stateful agents](#stateful-agents) - -## Defining an agent - -Two equivalent ways: the `Agent` class, or the `@agent` decorator. - -### The `Agent` class - -```python -from conductor.ai.agents import Agent - -agent = Agent( - name="greeter", # required; [a-zA-Z_][a-zA-Z0-9_-]* - model="openai/gpt-4o", # "provider/model" - instructions="You are a friendly assistant.", - tools=[], # @tool functions or ToolDef - max_turns=25, # agent-loop iteration cap - temperature=None, - max_tokens=None, -) -``` - -Common constructor arguments: `name`, `model`, `instructions`, `tools`, `agents`, -`strategy`, `guardrails`, `output_type`, `termination`, `handoffs`, `callbacks`, -`max_turns`, `max_tokens`, `temperature`, `reasoning_effort`, -`thinking_budget_tokens`, `credentials`, `stateful`, `include_contents`, -`timeout_seconds`. See the [API reference](api-reference.md#agent) for the full list. - -### The `@agent` decorator - -The docstring becomes the instructions. The decorated function stays callable. - -```python -from conductor.ai.agents import agent, tool - -@tool -def get_weather(city: str) -> str: - """Get current weather for a city.""" - return f"72F and sunny in {city}" - -@agent(model="openai/gpt-4o", tools=[get_weather]) -def weatherbot(): - """You are a weather assistant.""" -``` - -A `@agent` function resolves to an `Agent` automatically when passed as a sub-agent -or to `runtime.run(...)`. When `model` is omitted it inherits the parent's model. - -## Instructions - -Instructions can be a string, a callable, or a server-side `PromptTemplate`. - -```python -# Static string -Agent(name="a", model="openai/gpt-4o", instructions="You are concise.") - -# Dynamic — a @agent function that RETURNS a string is used as instructions -@agent(model="openai/gpt-4o") -def planner(): - rules = load_rules() # evaluated at resolution/compile time - return f"You are a planner. Follow these rules:\n{rules}" - -# Named server-side template -from conductor.ai.agents import Agent, PromptTemplate -Agent(name="t", model="openai/gpt-4o", - instructions=PromptTemplate(name="support_prompt", - variables={"tier": "${workflow.input.user_tier}"})) -``` - -`PromptTemplate` references a template already stored on the server (managed via the -Conductor UI/API); the SDK does not create templates. - -## Tools - -Decorate a plain function with `@tool`. Type hints and the docstring generate the -tool's JSON schema. Tools run as durable Conductor worker tasks. - -```python -from conductor.ai.agents import tool - -@tool -def calculate(expression: str) -> dict: - """Evaluate a math expression.""" - return {"result": eval(expression, {"__builtins__": {}}, {})} - -@tool(approval_required=True, timeout_seconds=60, retry_count=2) -def send_email(to: str, subject: str, body: str) -> dict: - """Send an email.""" # pauses for human approval before running - return {"status": "sent", "to": to} - -agent = Agent(name="assistant", model="openai/gpt-4o", - tools=[calculate, send_email]) -``` - -`@tool` keyword arguments: `name`, `external`, `approval_required`, -`timeout_seconds`, `guardrails`, `credentials`, `stateful`, `max_calls`, -`retry_count=2`, `retry_delay_seconds=2`, `retry_policy="linear_backoff"`. - -### Tool context - -A tool can receive execution context by declaring a `ToolContext` parameter; tools -without it are unchanged. - -```python -from conductor.ai.agents import tool, ToolContext - -@tool -def remember(note: str, context: ToolContext) -> str: - context.state["last_note"] = note # session_id, execution_id, state, ... - return "noted" -``` - -### Inspecting tool defs — `ToolRegistry` / `get_tool_defs` - -Each `@tool` function carries a resolved `ToolDef` (accessible via `get_tool_def`). -`get_tool_defs(tools)` extracts them from a mixed list. The runtime's `ToolRegistry` -registers tool functions as Conductor workers; you normally never touch it directly — -the runtime does it for you when you `run`/`serve`/`deploy`. - -```python -from conductor.ai.agents.tool import get_tool_def, get_tool_defs -defs = get_tool_defs([calculate, send_email]) -print(defs[0].name, defs[0].input_schema) -``` - -## Built-in tools - -These constructors return `ToolDef`s that compile to native Conductor tasks — most -need no worker process. Add them to `tools=[...]`. - -| Constructor | Purpose | -|---|---| -| `http_tool(name, description, url, method="GET", headers=None, input_schema=None, credentials=None, ...)` | Call an HTTP endpoint (HttpTask) | -| `api_tool(url, name=None, headers=None, tool_names=None, max_tools=64, credentials=None)` | Expand an OpenAPI/Swagger/Postman spec into tools | -| `mcp_tool(server_url, name=None, headers=None, tool_names=None, max_tools=64, credentials=None)` | Expose tools from an MCP server | -| `human_tool(name, description, input_schema=None)` | Pause for human input (HUMAN task) | -| `image_tool(name, description, llm_provider, model, ...)` | Generate images | -| `audio_tool(name, description, llm_provider, model, ...)` | Generate audio / TTS | -| `video_tool(name, description, llm_provider, model, ...)` | Generate video | -| `pdf_tool(name="generate_pdf", description=..., ...)` | Generate a PDF from markdown | -| `index_tool(name, description, vector_db, index, embedding_model_provider, embedding_model, ...)` | Index documents into a vector DB (RAG ingest) | -| `search_tool(name, description, vector_db, index, embedding_model_provider, embedding_model, max_results=5, ...)` | Search a vector DB (RAG query) | -| `wait_for_message_tool(name, description, batch_size=1, blocking=True)` | Dequeue from the workflow message queue | -| `agent_tool(agent, name=None, description=None, retry_count=None, retry_delay_seconds=None, optional=None)` | Call another `Agent` as a tool (sub-workflow) | - -```python -from conductor.ai.agents import Agent, http_tool, mcp_tool, agent_tool - -weather = http_tool( - name="weather", description="Current weather", - url="https://api.example.com/weather", method="GET", - input_schema={"type": "object", "properties": {"city": {"type": "string"}}}, -) - -mcp = mcp_tool(server_url="https://mcp.example.com/sse") - -sub = Agent(name="researcher", model="openai/gpt-4o", instructions="Research a topic.") -main = Agent(name="lead", model="openai/gpt-4o", tools=[weather, mcp, agent_tool(sub)]) -``` - -### RAG (`index_tool` + `search_tool`) - -`index_tool` writes embeddings into a vector DB; `search_tool` queries it. Both -compile to native Conductor LLM index/search tasks — give the agent both to build a -retrieval loop. - -### OCG retrieval sub-agent - -`ocg_agent(...)` builds a prebuilt retrieval `Agent` over an Open Context Graph; its -tools compile to plain HTTP tasks. `ocg_tools(...)` returns the raw `ToolDef`s if you -want to assemble your own retriever. - -```python -from conductor.ai.agents import Agent, agent_tool -from conductor.ai.agents.ocg import ocg_agent - -retriever = ocg_agent(model="anthropic/claude-sonnet-4-6", - url="https://ocg.example.com", credential="OCG_KEY") -main = Agent(name="support", model="openai/gpt-4o", tools=[agent_tool(retriever)]) -``` - -`url` is required and binds the instance; `credential` names a server-side credential -(the secret never appears in code). Agents bound to different OCG instances must use -distinct `name`s. - -## Multi-agent strategies - -Pass sub-agents via `agents=[...]` and pick a `strategy`. Strategy values -(`Strategy` enum or plain strings): - -| Strategy | Behavior | -|---|---| -| `HANDOFF` (default) | Parent LLM delegates to the right specialist (sub-agents appear as callable tools) | -| `SEQUENTIAL` | Run sub-agents in order, piping output forward | -| `PARALLEL` | Run sub-agents concurrently, then aggregate | -| `ROUTER` | A `router` (Agent or callable) picks one sub-agent per turn | -| `ROUND_ROBIN` | Cycle through sub-agents | -| `RANDOM` | Pick a sub-agent at random | -| `SWARM` | Sub-agents transfer control via [handoffs](#handoffs-swarm) | -| `MANUAL` | Caller selects the next agent | -| `PLAN_EXECUTE` | A planner emits a JSON plan that is executed deterministically — see [Advanced](advanced.md#plans-and-plan_execute) | - -```python -from conductor.ai.agents import Agent, Strategy - -billing = Agent(name="billing", model="openai/gpt-4o", instructions="Billing.") -tech = Agent(name="technical", model="openai/gpt-4o", instructions="Tech support.") - -support = Agent( - name="support", model="openai/gpt-4o", - instructions="Route the request to the right specialist.", - agents=[billing, tech], - strategy=Strategy.HANDOFF, -) -``` - -Sequential pipelines also have a shorthand with `>>`: - -```python -pipeline = extract >> summarize >> translate # Strategy.SEQUENTIAL -``` - -`scatter_gather(name, worker, ...)` builds a coordinator that fans a problem out to N -parallel copies of `worker` (via `agent_tool`) and synthesizes the results. - -## Handoffs (swarm) - -With `strategy="swarm"`, declare `handoffs=[...]` rules that transfer control between -agents after a tool call or after the LLM speaks. - -```python -from conductor.ai.agents import Agent -from conductor.ai.agents.handoff import OnTextMention, OnToolResult, OnCondition - -refund = Agent(name="refund", model="openai/gpt-4o", instructions="Process refunds.") - -support = Agent( - name="support", model="openai/gpt-4o", instructions="Help the customer.", - agents=[refund], strategy="swarm", - handoffs=[ - OnToolResult(tool_name="check_order", target="refund"), # after a tool runs - OnToolResult(tool_name="check_order", target="refund", result_contains="late"), - OnTextMention(text="refund", target="refund"), # LLM output contains text (case-insensitive) - OnCondition(condition=lambda ctx: ctx.get("iteration", 0) > 5, # custom predicate - target="refund"), - ], -) -``` - -`allowed_transitions={"a": ["b", "c"]}` constrains which agent may follow which. - -## Guardrails - -Guardrails validate input or output. They compile to worker tasks before/after the -LLM call. Decorate a `(str) -> GuardrailResult` function, or use the prebuilt -`RegexGuardrail` / `LLMGuardrail`. - -```python -from conductor.ai.agents import Agent, guardrail, GuardrailResult, RegexGuardrail, LLMGuardrail, Guardrail - -@guardrail -def no_pii(content: str) -> GuardrailResult: - """Reject responses containing an SSN.""" - import re - if re.search(r"\d{3}-\d{2}-\d{4}", content): - return GuardrailResult(passed=False, message="Remove the SSN.") - return GuardrailResult(passed=True) - -no_emails = RegexGuardrail(patterns=[r"[\w.+-]+@[\w-]+\.[\w.-]+"], - name="no_emails", message="No email addresses.") - -safety = LLMGuardrail(model="anthropic/claude-sonnet-4-6", - policy="Reject harmful or discriminatory content.") - -agent = Agent(name="safe", model="openai/gpt-4o", - guardrails=[Guardrail(no_pii, position="output", on_fail="retry"), - no_emails, safety]) -``` - -`Guardrail(func, position="input"|"output", on_fail="retry"|"raise"|"fix"|"human", -name=None, max_retries=3)`. On `on_fail="retry"` the failure message is fed back to -the LLM and it tries again; `"human"` (output only) pauses for a human; -`"fix"` substitutes `GuardrailResult.fixed_output`. - -## Termination and TextGate - -`termination=` accepts a composable `TerminationCondition`. Combine with `&` (all) -and `|` (any). - -```python -from conductor.ai.agents import ( - Agent, TextMentionTermination, MaxMessageTermination, - TokenUsageTermination, StopMessageTermination, -) - -stop = TextMentionTermination("DONE") | MaxMessageTermination(50) -stop = StopMessageTermination("TERMINATE") & TokenUsageTermination(max_total_tokens=10_000) - -agent = Agent(name="loop", model="openai/gpt-4o", termination=stop) -``` - -- `TextMentionTermination(text, case_sensitive=False)` — substring match in output. -- `StopMessageTermination(stop_message="TERMINATE")` — exact (stripped) match. -- `MaxMessageTermination(max_messages)` — message/iteration cap. -- `TokenUsageTermination(max_total_tokens=, max_prompt_tokens=, max_completion_tokens=)`. - -`TextGate` stops a `>>` pipeline early when an agent's output contains a sentinel, -compiled server-side (no worker round-trip): - -```python -from conductor.ai.agents.gate import TextGate -stage = Agent(name="triage", model="openai/gpt-4o", gate=TextGate("ESCALATE")) -``` - -## Callbacks - -Subclass `CallbackHandler` to hook the lifecycle. Each method receives keyword -arguments from the server and returns `None` to continue or a non-empty `dict` to -short-circuit (e.g. override the LLM response). Multiple handlers chain in list order. - -```python -from conductor.ai.agents import Agent, CallbackHandler - -class Logger(CallbackHandler): - def on_model_start(self, **kwargs): - print("calling LLM with", len(kwargs.get("messages", [])), "messages") - return None # continue - def on_tool_end(self, **kwargs): - print("tool", kwargs.get("tool_name"), "done") - return None - -agent = Agent(name="watched", model="openai/gpt-4o", callbacks=[Logger()]) -``` - -Hook points: `on_agent_start`, `on_agent_end`, `on_model_start`, `on_model_end`, -`on_tool_start`, `on_tool_end`. (The old `before_model_callback`/`after_model_callback` -constructor args are deprecated — use `callbacks=[...]`.) - -## Streaming and human-in-the-loop - -`runtime.start(...)` returns an [`AgentHandle`](api-reference.md#agenthandle); iterate -`handle.stream()` for [`AgentEvent`](api-reference.md#agentevent)s. When a tool needs -human approval (`@tool(approval_required=True)`) or input (`human_tool`), the stream -emits a `WAITING` event and the workflow pauses. - -```python -from conductor.ai.agents import Agent, AgentRuntime, EventType, tool - -@tool(approval_required=True) -def transfer_funds(from_acct: str, to_acct: str, amount: float) -> dict: - """Transfer money; pauses for human approval first.""" - return {"status": "completed", "amount": amount} - -agent = Agent(name="banker", model="openai/gpt-4o", tools=[transfer_funds]) - -with AgentRuntime() as runtime: - handle = runtime.start(agent, "Transfer $500 from ACC-1 to ACC-2.") - for event in handle.stream(): - if event.type == EventType.TOOL_CALL: - print("tool_call", event.tool_name, event.args) - elif event.type == EventType.WAITING: - handle.approve() # or handle.reject("not authorized") - elif event.type == EventType.DONE: - print("done:", event.output) -``` - -HITL methods on the handle (and on a stream): - -- `approve(*, event=None)` — approve the pending tool call. -- `reject(reason="", *, event=None)` — reject it. -- `respond(output, *, event=None)` — answer a `human_tool` with arbitrary fields. -- `send(message, *, event=None)` — push a message to a waiting (multi-turn) agent. - -Pass `event=` to target a specific pending pause when more than one -is in flight (event-targeted approval): - -```python -for event in handle.stream(): - if event.type == EventType.WAITING: - handle.approve(event=event) # approve exactly this pending call -``` - -`runtime.run(agent, prompt, on_event=callback)` runs synchronously while streaming -events to `callback`. Async variants: `runtime.stream_async`, `await handle.approve_async(...)`, -`handle.stream_async()`. - -`EventType` values: `THINKING`, `TOOL_CALL`, `TOOL_RESULT`, `HANDOFF`, `WAITING`, -`MESSAGE`, `ERROR`, `DONE`, `GUARDRAIL_PASS`, `GUARDRAIL_FAIL`. - -## Schedules - -Run an agent on a cron schedule. Define `Schedule`s and attach them at deploy time, or -manage them through the schedule client. - -```python -from conductor.ai.agents import AgentRuntime, Schedule - -nightly = Schedule(name="nightly", cron="0 0 * * *", timezone="UTC", - input={"prompt": "Summarize today's tickets."}) - -with AgentRuntime() as runtime: - runtime.deploy(agent, schedules=[nightly]) # upsert these, prune the rest -``` - -`schedules=[]` purges all schedules for the agent; omitting `schedules` leaves them -untouched. The schedule lifecycle client (`runtime.schedules_client()` or -`runtime.client.schedules`) exposes `pause`, `resume`, `delete`, `run_now`, -`preview_next`, `reconcile`, plus the native `get_schedule`/`save_schedule`/ -`get_all_schedules` for reads, writes, and lists. See -[Advanced](advanced.md) and the [API reference](api-reference.md#schedule). - -## Agents from a class - -`Agent.from_instance(obj)` turns `@agent`-decorated **methods** on an object into -agents — handy for dependency injection and grouping related agents, tools, and -guardrails on one class. `@tool` and `@guardrail` methods on the same instance are -auto-attached (bound to `self`). - -```python -from conductor.ai.agents import Agent, agent, tool - -class Support: - def __init__(self, db): - self.db = db - - @tool - def lookup(self, order_id: str) -> dict: - """Look up an order.""" - return self.db.get(order_id) - - @agent(model="openai/gpt-4o") - def triage(self): - """Triage the request and answer using the lookup tool.""" - -support = Support(db=my_db) - -one = Agent.from_instance(support, "triage") # a single Agent by name -allg = Agent.from_instance(support) # list[Agent], one per @agent method -``` - -Sub-agents can be referenced by method name as strings in the `@agent`'s `agents=` -list; they resolve against sibling `@agent` methods (cycles raise). A method returning -a string provides dynamic instructions; returning an `Agent` makes it a factory. - -## Stateful agents - -Set `stateful=True` to scope the agent's (and its tools') worker tasks to a per-run -domain so state isn't shared across concurrent executions. Use it when a tool holds -per-execution state. - -```python -agent = Agent(name="session_agent", model="openai/gpt-4o", - tools=[remember], stateful=True) -``` - -For conversational continuity across `run` calls, pass a `session_id`: - -```python -runtime.run(agent, "My name is Ada.", session_id="user-42") -runtime.run(agent, "What's my name?", session_id="user-42") -``` +- [Agents](concepts/agents.md) and [tools](concepts/tools.md) +- [Multi-agent strategies](concepts/multi-agent.md), [guardrails](concepts/guardrails.md), and [termination](concepts/termination.md) +- [Streaming/HITL](concepts/streaming-hitl.md), [stateful agents](concepts/stateful.md), and [scheduling](concepts/scheduling.md) +- [Agent API map](reference/api.md) diff --git a/docs/api-map.md b/docs/api-map.md new file mode 100644 index 000000000..7a0a3cbb8 --- /dev/null +++ b/docs/api-map.md @@ -0,0 +1,15 @@ +# Core API map + +| Need | Python type | Reference | +|---|---|---| +| Configure transport and auth | `Configuration` / `OrkesClients` | [connection/authentication](connection-authentication.md) | +| Run workflow executions | `WorkflowExecutor` / `WorkflowClient` | [WORKFLOW.md](WORKFLOW.md) | +| Poll and update tasks | `TaskHandler` / `TaskClient` | [TASK_MANAGEMENT.md](TASK_MANAGEMENT.md) | +| Manage definitions | `MetadataClient` | [METADATA.md](METADATA.md) | +| Schedule workflows | `SchedulerClient` | [SCHEDULE.md](SCHEDULE.md) | +| Manage schemas | `SchemaClient` | [schema client](schema-client.md) | +| Manage secrets and integrations | `SecretClient` / `IntegrationClient` | [security](security.md) | +| Compile, deploy, run, signal agents | `AgentClient` / `AgentRuntime` | [agent control plane](agents/reference/client.md) | + +Python does not currently expose a public workflow-scoped `FileClient`; use the +server and task capabilities appropriate to your deployment instead. diff --git a/docs/compatibility.md b/docs/compatibility.md new file mode 100644 index 000000000..dbcfba33d --- /dev/null +++ b/docs/compatibility.md @@ -0,0 +1,24 @@ +# Compatibility matrix + +| Area | Supported baseline | Notes | +|---|---|---| +| Python SDK | Python 3.10+ | Defined by `pyproject.toml`. | +| OSS Conductor | Supported server deployment | Test the target server during upgrades. | +| Orkes | Supported tenant API | Enterprise features depend on tenant permissions. | +| Conductor agents | Server agent runtime and provider integration | Provider credentials live on the server. | + +The Python SDK does not currently provide Java's `FileClient` or Spring Boot +modules. This documentation does not present either as available Python support. + +## Workflow-scoped files + +The Python SDK does not currently expose a public workflow-scoped `FileClient`. +Use task-appropriate object storage or a server capability selected by your +deployment; do not copy Java `FileClient` examples into Python applications. + +## Spring framework integration + +Spring and Spring Boot modules are Java-specific. Host Python workers and +Conductor-agent services in the application framework your deployment uses; see +the [FastAPI worker example](../examples/fastapi_worker_service.py) and +[deployment/scaling](deployment-scaling.md). diff --git a/docs/connection-authentication.md b/docs/connection-authentication.md new file mode 100644 index 000000000..0fdd2df40 --- /dev/null +++ b/docs/connection-authentication.md @@ -0,0 +1,19 @@ +# Connection and authentication + +`Configuration()` reads `CONDUCTOR_SERVER_URL`, then defaults to +`http://localhost:8080/api`. It reads `CONDUCTOR_AUTH_KEY` and +`CONDUCTOR_AUTH_SECRET` together when the server requires key/secret auth. + +```python +from conductor.client.configuration.configuration import Configuration + +config = Configuration() +print(config.host) +``` + +**OSS:** a local development server may allow anonymous access. **Orkes:** use +the tenant API endpoint and an application access key. Never put credentials in +workflow inputs, agent prompts, or source control. + +If requests fail, verify that the URL ends in `/api`, the server is reachable, +and the credentials belong to that endpoint. Next: [core quickstart](core-quickstart.md). diff --git a/docs/core-quickstart.md b/docs/core-quickstart.md new file mode 100644 index 000000000..f86b2cca4 --- /dev/null +++ b/docs/core-quickstart.md @@ -0,0 +1,17 @@ +# Core workflow and worker quickstart + +**Prerequisites:** Python 3.10+, `pip install conductor-python`, and a reachable +Conductor server from [server setup](server-setup.md). + +Run the maintained example from its directory so its sibling workflow module is +importable: + +```shell +cd examples/helloworld +python helloworld.py +``` + +Expected result: the workflow completes and prints `workflow result: Hello World`. +The example registers its workflow, starts a local worker, executes the workflow, +and stops the worker. If the task stays scheduled, verify that the worker is +polling the exact task type. Continue with [workers](workers.md) or [workflows](workflows.md). diff --git a/docs/debugging.md b/docs/debugging.md new file mode 100644 index 000000000..effd5425a --- /dev/null +++ b/docs/debugging.md @@ -0,0 +1,15 @@ +# Debugging incidents + +Start with safe evidence: workflow ID, task reference, status, retry count, and +`reasonForIncompletion`. Confirm server reachability and authentication before +changing application code. + +| Symptom | First check | +|---|---| +| Connection error | `CONDUCTOR_SERVER_URL` includes `/api` and the server is healthy. | +| Task remains scheduled | A worker polls the exact task type and domain. | +| Authentication failure | `CONDUCTOR_AUTH_KEY` and `CONDUCTOR_AUTH_SECRET` target the active server. | +| Agent cannot call a model | The provider credential is configured on the server. | + +Use [WORKFLOW.md](WORKFLOW.md) and [agents/reference/client.md](agents/reference/client.md) +for the relevant inspection APIs. diff --git a/docs/deployment-scaling.md b/docs/deployment-scaling.md new file mode 100644 index 000000000..309c5d0f7 --- /dev/null +++ b/docs/deployment-scaling.md @@ -0,0 +1,9 @@ +# Deployment, scaling, and graceful shutdown + +Run workers and `AgentRuntime.serve()` processes as long-lived services. Scale by +adding worker instances and use task domains or queue limits to isolate workloads. +On shutdown, stop task handlers and runtimes so in-flight tasks can be redelivered +instead of being abandoned. + +Do not construct a new runtime per web request. Reuse clients and runtimes for the +application lifetime; use [reliability](reliability.md) for timeout and retry policy. diff --git a/docs/design/AGENT_SDK_PORTING_SPEC.md b/docs/design/AGENT_SDK_PORTING_SPEC.md index 467497924..51200e3b3 100644 --- a/docs/design/AGENT_SDK_PORTING_SPEC.md +++ b/docs/design/AGENT_SDK_PORTING_SPEC.md @@ -146,16 +146,16 @@ getAuthenticationHeaders() -> { "X-Authorization": } | null **Contract:** 1. **Host resolution order** (when not passed explicitly): - `CONDUCTOR_SERVER_URL` → `AGENTSPAN_SERVER_URL` → default + `CONDUCTOR_SERVER_URL` → `CONDUCTOR_SERVER_URL` → default `http://localhost:8080/api`. 2. **Log level**: `Configuration` accepts an optional `logLevel` (level name or numeric level). Resolution order: explicit param → `CONDUCTOR_LOG_LEVEL` env → - `AGENTSPAN_LOG_LEVEL` env → derived from the `debug` flag (DEBUG/INFO). This is + `CONDUCTOR_LOG_LEVEL` env → derived from the `debug` flag (DEBUG/INFO). This is the **only** log-level setting in the SDK; the agent runtime reads it from `Configuration` (see R4 — `AgentConfig` has no log level). **Acceptance criteria** -- [ ] With only `AGENTSPAN_SERVER_URL` set, `Configuration()` resolves it; with +- [ ] With only `CONDUCTOR_SERVER_URL` set, `Configuration()` resolves it; with both set, `CONDUCTOR_SERVER_URL` wins. - [ ] `Configuration(logLevel="WARNING")` and `CONDUCTOR_LOG_LEVEL=WARNING` both yield WARNING; the agent runtime applies it to the agent loggers. @@ -176,15 +176,15 @@ creating two sources of truth. Connection, auth, and log level now come from | Field | Type | Default | Env var | |---|---|---|---| -| `workerPollIntervalMs` | int | 100 | `AGENTSPAN_WORKER_POLL_INTERVAL` | -| `workerThreadCount` | int | 1 | `AGENTSPAN_WORKER_THREADS` | -| `autoStartWorkers` | bool | true | `AGENTSPAN_AUTO_START_WORKERS` | -| `daemonWorkers` | bool | true | `AGENTSPAN_DAEMON_WORKERS` | -| `autoRegisterIntegrations` | bool | false | `AGENTSPAN_INTEGRATIONS_AUTO_REGISTER` | -| `streamingEnabled` | bool | true | `AGENTSPAN_STREAMING_ENABLED` | -| `livenessEnabled` | bool | true | `AGENTSPAN_LIVENESS_ENABLED` | -| `livenessStallSeconds` | float | 30.0 | `AGENTSPAN_LIVENESS_STALL_SECONDS` | -| `livenessCheckIntervalSeconds` | float | 10.0 | `AGENTSPAN_LIVENESS_CHECK_INTERVAL_SECONDS` | +| `workerPollIntervalMs` | int | 100 | `CONDUCTOR_AGENT_WORKER_POLL_INTERVAL` | +| `workerThreadCount` | int | 1 | `CONDUCTOR_AGENT_WORKER_THREADS` | +| `autoStartWorkers` | bool | true | `CONDUCTOR_AGENT_AUTO_START_WORKERS` | +| `daemonWorkers` | bool | true | `CONDUCTOR_AGENT_DAEMON_WORKERS` | +| `autoRegisterIntegrations` | bool | false | `CONDUCTOR_AGENT_INTEGRATIONS_AUTO_REGISTER` | +| `streamingEnabled` | bool | true | `CONDUCTOR_AGENT_STREAMING_ENABLED` | +| `livenessEnabled` | bool | true | `CONDUCTOR_AGENT_LIVENESS_ENABLED` | +| `livenessStallSeconds` | float | 30.0 | `CONDUCTOR_AGENT_LIVENESS_STALL_SECONDS` | +| `livenessCheckIntervalSeconds` | float | 10.0 | `CONDUCTOR_AGENT_LIVENESS_CHECK_INTERVAL_SECONDS` | `AgentConfig.fromEnv()` reads the env vars above. It MUST NOT read server URL, credentials, or log level. @@ -229,7 +229,7 @@ FUNCTION startInternal(agent, prompt, runSettings?, media?, sessionId?, payload = { agentConfig: configJson, prompt: prompt, sessionId: sessionId ?? "", media: media ?? [] } // optional keys, only when set: - // context, idempotencyKey, timeoutSeconds, credentials, runId + // context, idempotencyKey, timeoutSeconds, runId data = agentClient.startAgent(payload) // server: compile+register+start executionId = data["executionId"] requiredWorkers = data["requiredWorkers"] // optional set of task names @@ -284,11 +284,11 @@ FUNCTION resolveSecrets(task, declaredNames): a clear error. The worker MUST NOT fall back to reading the ambient process environment. - **RETIRE** the execution-token flow: remove any `executionToken` / - `__agentspan_ctx__` handling and any `POST /workers/secrets` client code. + `__conductor_agent_ctx__` handling and any `POST /workers/secrets` client code. **Server dependency note:** this contract requires a server that persists `TaskDef.runtimeMetadata` and delivers `Task.runtimeMetadata` -(conductor-oss PR #1255 / agentspan server > 0.4.2). Integration tests SHOULD +(conductor-oss PR #1255 / Conductor Agents server > 0.4.2). Integration tests SHOULD capability-probe (register a TaskDef with `runtimeMetadata`, read it back; skip the suite if the server drops the field). @@ -492,7 +492,7 @@ Remove (and ensure nothing references them): its own JWT) and any DX wrapper client around them. - [ ] The credentials **fetcher** (execution-token + `POST /workers/secrets`). - [ ] Agent **server auto-start / detection** logic and its config flag - (`autoStartServer` / `AGENTSPAN_AUTO_START_SERVER`). + (`autoStartServer` / `CONDUCTOR_AGENT_AUTO_START_SERVER`). - [ ] The **parallel token cache** (standalone `POST /token` mint helper) — keep at most a JWT-`exp` decoder. - [ ] Dead `AgentConfig` fields (R4 list) and any `toConductorConfiguration()` @@ -515,7 +515,7 @@ observed in production runs and are now part of the contract: first were **silently dropped** — the model's fan-out intent vanished with no trace in any task output. -The server compiler (conductor `agentspan-server` `MultiAgentCompiler`) now +The server compiler (conductor `conductor_agent-server` `MultiAgentCompiler`) now generates transfer tools with a required `message` argument ("hand-off note"), records honored hand-offs in the conversation as `[source -> target]: `, drops `[]`/`{}` tool-call-only results from @@ -620,12 +620,14 @@ open (auth-disabled) server — treat as anonymous. "context": { }, // optional "idempotencyKey": "…", // optional "timeoutSeconds": 120, // optional - "credentials": ["GH_TOKEN"], // optional — names to resolve for this run "runId": "…", // optional — stateful worker-domain routing "static_plan": { } // optional — PLAN_EXECUTE fixed plan } ``` +Credential names are local worker configuration. They populate task-definition runtime metadata +and are not part of the agent start request or workflow input. + ### 3.4 `agentConfig` LLM wire keys (subset relevant to RunSettings) Top level of the serialized agent config: diff --git a/docs/design/WORKER_SDK_IMPLEMENTATION_GUIDE.md b/docs/design/WORKER_SDK_IMPLEMENTATION_GUIDE.md index 915fbc361..73eba35c0 100644 --- a/docs/design/WORKER_SDK_IMPLEMENTATION_GUIDE.md +++ b/docs/design/WORKER_SDK_IMPLEMENTATION_GUIDE.md @@ -2488,10 +2488,10 @@ duplicate server URL, credentials, or log level in `AgentConfig`. | `liveness_stall_seconds` | float | 30.0 | Idle window before a run is deemed stalled | | `liveness_check_interval_seconds` | float | 10.0 | Liveness poll cadence | -`AgentConfig.from_env()` reads the SDK's env prefix (`AGENTSPAN_*`). Connection/auth/log +`AgentConfig.from_env()` reads the SDK's env prefix (`CONDUCTOR_AGENT_*`). Connection/auth/log resolution lives in `Configuration`: host from `CONDUCTOR_SERVER_URL` → -`AGENTSPAN_SERVER_URL`; auth from `CONDUCTOR_AUTH_*`; log level from -`CONDUCTOR_LOG_LEVEL` / `AGENTSPAN_LOG_LEVEL` (or the `debug` flag). +`CONDUCTOR_SERVER_URL`; auth from `CONDUCTOR_AUTH_*`; log level from +`CONDUCTOR_LOG_LEVEL` / `CONDUCTOR_LOG_LEVEL` (or the `debug` flag). ### 25.5 RunSettings (per-run LLM overrides) diff --git a/docs/documentation-parity.md b/docs/documentation-parity.md new file mode 100644 index 000000000..df121203b --- /dev/null +++ b/docs/documentation-parity.md @@ -0,0 +1,20 @@ +# Java/Python documentation parity + +The Python SDK follows the same documentation information architecture as the +Java SDK while mapping instructions to the Python public API. This page makes the +intentional differences explicit so a missing page is not mistaken for support. + +| Java documentation capability | Python documentation counterpart | Status | +|---|---|---| +| Server, connection, quickstart, workflows, workers, lifecycle, testing | [Core guides](README.md#build) | Supported with Python APIs | +| Schema client, schedules/events, reliability, security, deployment, observability, debugging | [Operations guides](README.md#operate) | Supported where the target server exposes the capability | +| Agent concepts, runtime, API/client, definition contract | [Conductor agent guide](agents/README.md) | Supported with Python APIs | +| Google ADK and framework bridges | [Framework bridges](agents/README.md#framework-bridges) | Python includes Google ADK, LangChain, LangGraph, OpenAI Agents, and Claude Agent SDK bridges | +| Workflow-scoped FileClient | [Compatibility](compatibility.md#workflow-scoped-files) | Not currently exposed as a public Python client | +| Spring and Spring Boot integration | [Compatibility](compatibility.md#spring-framework-integration) | Java-specific; use Python application hosting patterns instead | + +## Maintenance rule + +When adding a Java-style guide or a Python capability, update this map and the +documentation hub in the same change. Do not claim a Java-only client or framework +feature is available in Python. diff --git a/docs/documentation-standard.md b/docs/documentation-standard.md new file mode 100644 index 000000000..2cd6ad756 --- /dev/null +++ b/docs/documentation-standard.md @@ -0,0 +1,11 @@ +# Documentation standard + +Every primary Python SDK guide must include its audience and prerequisites, an +OSS/Orkes capability label when behavior differs, and a security note when it +handles credentials, user data, tools, or external side effects. + +Commands must be runnable or marked **Fragment** and linked to a complete +repository example. State the expected result, common failure modes, cleanup, +and next steps. Use `conductor-python` and published PyPI versions rather than +stale pinned versions. CI validates internal Markdown links and curated example +paths. diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 000000000..c2c14960d --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,14 @@ +# Recommended examples + +The [examples catalog](../examples/README.md) is broad; start with these maintained +paths and review their side effects before using real credentials. + +| Path | Use | Expected result | +|---|---|---| +| [Hello World](../examples/helloworld/helloworld.py) | Core workflow and worker | Prints a completed workflow result. | +| [Basic agent](../examples/agents/01_basic_agent.py) | First Conductor agent | Prints an `AgentResult`. | +| [Plan and compile](../examples/agents/103_plan_and_compile.py) | Inspect an agent workflow | Prints the compiled plan/workflow shape. | +| [Agent quickstart](../examples/agents/quickstart/README.md) | Focused agent patterns | Runs the selected quickstart script. | + +Stop local servers and worker processes after experimenting. Provider credentials +must be configured on the Conductor server. diff --git a/docs/observability.md b/docs/observability.md new file mode 100644 index 000000000..12d28d4b1 --- /dev/null +++ b/docs/observability.md @@ -0,0 +1,8 @@ +# Metrics and logging + +Use SDK logging and the metrics collectors to observe polling, task execution, +workflow latency, retries, and failures. Configure log level with +`CONDUCTOR_LOG_LEVEL`; use `METRICS.md` for metric names and Prometheus setup. + +For agent executions, inspect the shared workflow record for inputs, outputs, tool +calls, retries, and status. Avoid logging credentials or unredacted sensitive data. diff --git a/docs/reliability.md b/docs/reliability.md new file mode 100644 index 000000000..91a608f4c --- /dev/null +++ b/docs/reliability.md @@ -0,0 +1,9 @@ +# Reliability: timeouts, retries, idempotency, and domains + +Set poll, response, and execution timeouts on every worker task definition. Retry +only idempotent or compensated work, use exponential backoff for transient remote +failures, and route resource-bound work to domains. + +Workers may receive a task more than once. Persist idempotency keys before external +side effects and use `TaskInProgress` or lease extension for long-running work. +Verify behavior with failure-path tests and inspect task reasons before retrying. diff --git a/docs/schedules-events.md b/docs/schedules-events.md new file mode 100644 index 000000000..7aa5d0cda --- /dev/null +++ b/docs/schedules-events.md @@ -0,0 +1,13 @@ +# Schedules and events + +Use `SchedulerClient` for workflow schedules and the event client for event-driven +integration. Give scheduled executions a stable correlation or idempotency key so +retries do not duplicate business effects. + +```python +scheduler = clients.get_scheduler_client() +# scheduler.save_schedule(...) +``` + +See [SCHEDULE.md](SCHEDULE.md) for the complete schedule request model and +[workflow lifecycle](workflow-lifecycle.md) for safe operational handling. diff --git a/docs/schema-client.md b/docs/schema-client.md new file mode 100644 index 000000000..6496f0ff6 --- /dev/null +++ b/docs/schema-client.md @@ -0,0 +1,9 @@ +# Schema client + +`SchemaClient` manages versioned schema definitions through the Conductor schema +API. Obtain it from `OrkesClients` when the connected server exposes the schema +service, then save, fetch, list, or delete schemas. + +**OSS/Orkes:** availability depends on the server deployment and permissions. +Validate schemas in a non-production environment before making them required by +workers. The generated request and model types are listed in [api-map](api-map.md). diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 000000000..4c4e3743a --- /dev/null +++ b/docs/security.md @@ -0,0 +1,9 @@ +# Security and secrets + +Keep API keys, provider credentials, and signing secrets in the Conductor server +or its configured secret provider. Do not put them in workflow inputs, agent +prompts, task output, example source, or version control. + +Agent tools declare required credentials; a capable server delivers resolved values +only in task runtime metadata. Missing credentials fail before tool execution. +See [SECRET_MANAGEMENT.md](SECRET_MANAGEMENT.md) and [agent tools](agents/concepts/tools.md). diff --git a/docs/server-setup.md b/docs/server-setup.md new file mode 100644 index 000000000..e9480120c --- /dev/null +++ b/docs/server-setup.md @@ -0,0 +1,31 @@ +# Connect the Python SDK to a Conductor server + +**Audience:** developers running workflows or Conductor agents locally or against a hosted cluster. + +## Hosted or remote server + +Set the API URL, including `/api`, then add credentials only when the target +requires them: + +```shell +export CONDUCTOR_SERVER_URL=https://your-server.example/api +export CONDUCTOR_AUTH_KEY= +export CONDUCTOR_AUTH_SECRET= +``` + +## Local development + +The Conductor CLI is the preferred local path: + +```shell +conductor server start +conductor server status +export CONDUCTOR_SERVER_URL=http://localhost:8080/api +``` + +Docker is an alternative: `docker run --rm -p 8080:8080 conductoross/conductor:latest`. +Stop CLI-managed servers with `conductor server stop` and containers with +`docker stop `. + +For agent runs, configure the LLM provider credential on the server before +starting it. Continue with [connection/authentication](connection-authentication.md). diff --git a/docs/upgrading.md b/docs/upgrading.md new file mode 100644 index 000000000..ff6db7792 --- /dev/null +++ b/docs/upgrading.md @@ -0,0 +1,10 @@ +# Upgrade the Python SDK safely + +Before upgrading, read the release notes, test against the target Conductor +server, and pin the new package version in a staging environment. Run unit tests +and one representative workflow and agent execution before production rollout. + +The canonical agent configuration names use `CONDUCTOR_AGENT_*`. Legacy +configuration aliases continue to work for compatibility, but new deployments +should use the canonical names. Roll back by restoring the prior package lock and +keeping workflow definitions versioned rather than changing active behavior in place. diff --git a/docs/workers.md b/docs/workers.md new file mode 100644 index 000000000..dc77530d5 --- /dev/null +++ b/docs/workers.md @@ -0,0 +1,17 @@ +# Workers + +Workers poll a named task queue, execute idempotent business logic, and return a +result. Define a function worker with `@worker_task`, then run it with +`TaskHandler` or `AsyncTaskRunner`. + +```python +from conductor.client.worker.worker_task import worker_task + +@worker_task(task_definition_name="greet") +def greet(name: str) -> dict: + return {"result": f"Hello {name}"} +``` + +Use explicit retries, task timeouts, and domains for production isolation. Stop +task handlers during application shutdown. See the detailed [worker guide](WORKER.md), +[reliability](reliability.md), and [workflow testing](workflow-testing.md). diff --git a/docs/workflow-lifecycle.md b/docs/workflow-lifecycle.md new file mode 100644 index 000000000..3d4b75aac --- /dev/null +++ b/docs/workflow-lifecycle.md @@ -0,0 +1,11 @@ +# Workflow lifecycle and versioning + +Register a versioned workflow, start it through `WorkflowExecutor`, and inspect +its execution before changing behavior. Additive output changes are normally +safe; renamed inputs, removed outputs, and changed task references are breaking +and require a new workflow version. + +Use pause/resume for controlled maintenance, retry only transient failures, and +terminate executions with an explicit reason. Inspect failed tasks before retrying +to avoid replaying unsafe side effects. See [WORKFLOW.md](WORKFLOW.md) for the +complete lifecycle API. diff --git a/docs/workflow-testing.md b/docs/workflow-testing.md new file mode 100644 index 000000000..4f070b93a --- /dev/null +++ b/docs/workflow-testing.md @@ -0,0 +1,13 @@ +# Workflow testing + +Use the SDK workflow-test utilities to validate definitions with controlled task +outputs before deploying. Cover successful paths, retryable and terminal failures, +branching, timeouts, and workflow outputs. + +```shell +python -m pytest tests/unit +``` + +The maintained examples and detailed API are in [WORKFLOW_TESTING.md](WORKFLOW_TESTING.md). +Do not run examples with production credentials unless their external side effects +have been reviewed. diff --git a/docs/workflows.md b/docs/workflows.md new file mode 100644 index 000000000..5edebc2f8 --- /dev/null +++ b/docs/workflows.md @@ -0,0 +1,18 @@ +# Workflows + +Use `ConductorWorkflow` to build a workflow definition and `WorkflowExecutor` +to register, start, inspect, pause, resume, retry, or terminate executions. + +```python +from conductor.client.workflow.conductor_workflow import ConductorWorkflow + +workflow = ConductorWorkflow(name="greetings", version=1, executor=executor) +workflow >> greet(task_ref_name="greet", name=workflow.input("name")) +workflow.output_parameters({"result": "${greet.output.result}"}) +workflow.register(overwrite=True) +``` + +Use system task classes for HTTP, wait, switch, fork/join, sub-workflow, and +other orchestration primitives. See the detailed [workflow API guide](WORKFLOW.md) +and [workflow lifecycle](workflow-lifecycle.md). Keep versioned definitions +compatible with callers; never place secrets in workflow input. diff --git a/e2e/conftest.py b/e2e/conftest.py index 442fa008a..e5098dfc2 100644 --- a/e2e/conftest.py +++ b/e2e/conftest.py @@ -1,18 +1,16 @@ -"""E2E test infrastructure. No mocks. Real server, real CLI, real services.""" +"""E2E test infrastructure. No mocks. Real server and real services.""" import os -import subprocess import pytest import requests # ── Configuration from env (set by orchestrator) ──────────────────────── -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") BASE_URL = SERVER_URL.rstrip("/").replace("/api", "") -CLI_PATH = os.environ.get("AGENTSPAN_CLI_PATH", "agentspan") MCP_TESTKIT_URL = os.environ.get("MCP_TESTKIT_URL", "http://localhost:3001") -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") # ── Markers ───────────────────────────────────────────────────────────── @@ -86,62 +84,6 @@ def mcp_url(): return MCP_TESTKIT_URL -# ── CLI credential helper ────────────────────────────────────────────── - - -class CredentialsCLI: - """Wraps the agentspan CLI for credential operations. - - The CLI expects AGENTSPAN_SERVER_URL without the /api suffix - (e.g., http://localhost:8080). It appends /api internally. - """ - - def __init__(self, cli_path: str, server_url: str): - self._cli = cli_path - # CLI expects base URL without /api — strip it if present - self._server_url = server_url.rstrip("/").removesuffix("/api") - - def _run(self, *args: str) -> subprocess.CompletedProcess: - cmd = [self._cli] + list(args) - env = {**os.environ, "AGENTSPAN_SERVER_URL": self._server_url} - return subprocess.run( - cmd, capture_output=True, text=True, timeout=15, env=env - ) - - def set(self, name: str, value: str) -> None: - result = self._run("credentials", "set", name, value) - if result.returncode != 0: - # conductor-oss standalone serves secrets from the server process - # env — the store is read-only there, so the write-dependent - # lifecycle steps cannot run (a server-flavor capability, not an - # SDK regression; mirrors the Java/C# ports' assumption-skip). - if "read-only" in result.stderr.lower(): - pytest.skip( - "server secret store is read-only (env-backed) — " - "skipping write-dependent step" - ) - raise AssertionError(f"credentials set {name} failed: {result.stderr}") - - def delete(self, name: str) -> None: - result = self._run("credentials", "delete", name) - # Ignore "not found" and "read-only" errors during cleanup — best-effort - stderr = result.stderr.lower() - if result.returncode != 0 and "not found" not in stderr and "read-only" not in stderr: - raise AssertionError( - f"credentials delete {name} failed: {result.stderr}" - ) - - def list(self) -> str: - result = self._run("credentials", "list") - assert result.returncode == 0, f"credentials list failed: {result.stderr}" - return result.stdout - - -@pytest.fixture(scope="session") -def cli_credentials(): - return CredentialsCLI(CLI_PATH, SERVER_URL) - - # ── Server API helpers ────────────────────────────────────────────────── diff --git a/e2e/test_suite10_code_execution.py b/e2e/test_suite10_code_execution.py index 731c4012c..f2c70e9f3 100644 --- a/e2e/test_suite10_code_execution.py +++ b/e2e/test_suite10_code_execution.py @@ -74,7 +74,7 @@ def _jupyter_available() -> bool: def _get_workflow(execution_id): """Fetch workflow execution from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() diff --git a/e2e/test_suite11_langgraph.py b/e2e/test_suite11_langgraph.py index 8ddc5e63f..789b8c5be 100644 --- a/e2e/test_suite11_langgraph.py +++ b/e2e/test_suite11_langgraph.py @@ -34,7 +34,7 @@ pytestmark = [pytest.mark.e2e] TIMEOUT = 120 -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") BASE_URL = SERVER_URL.rstrip("/").replace("/api", "") diff --git a/e2e/test_suite12_termination_gates.py b/e2e/test_suite12_termination_gates.py index 68fdb762b..86deaac9e 100644 --- a/e2e/test_suite12_termination_gates.py +++ b/e2e/test_suite12_termination_gates.py @@ -28,7 +28,7 @@ pytestmark = [pytest.mark.e2e] -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") TIMEOUT = 120 @@ -50,7 +50,7 @@ def echo_tool(text: str) -> str: def _get_workflow(execution_id): """Fetch workflow execution from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() diff --git a/e2e/test_suite13_callbacks.py b/e2e/test_suite13_callbacks.py index 132d40812..d7428658f 100644 --- a/e2e/test_suite13_callbacks.py +++ b/e2e/test_suite13_callbacks.py @@ -17,7 +17,7 @@ pytestmark = [pytest.mark.e2e] -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") TIMEOUT = 120 @@ -100,7 +100,7 @@ def on_tool_end(self, **kwargs): def _get_workflow(execution_id): """Fetch workflow execution from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() diff --git a/e2e/test_suite14_stateful_domain.py b/e2e/test_suite14_stateful_domain.py index 33f87b9ba..76e1aad33 100644 --- a/e2e/test_suite14_stateful_domain.py +++ b/e2e/test_suite14_stateful_domain.py @@ -36,7 +36,7 @@ ] TIMEOUT = 300 # 5 min per run -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") BASE_URL = SERVER_URL.rstrip("/").replace("/api", "") diff --git a/e2e/test_suite15_skills.py b/e2e/test_suite15_skills.py index cac0c25bf..ab51c9dd2 100644 --- a/e2e/test_suite15_skills.py +++ b/e2e/test_suite15_skills.py @@ -25,7 +25,7 @@ pytestmark = pytest.mark.e2e -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") DG_SKILL_PATH = Path("~/.claude/skills/dg").expanduser() diff --git a/e2e/test_suite16_cli_skills.py b/e2e/test_suite16_cli_skills.py deleted file mode 100644 index 0b531988f..000000000 --- a/e2e/test_suite16_cli_skills.py +++ /dev/null @@ -1,485 +0,0 @@ -"""Suite 16: CLI Skills — real CLI skill run/load/serve paths. - -No mocks. Requires: -- a live AgentSpan server -- an installed/built agentspan CLI (AGENTSPAN_CLI_PATH or PATH) -- an LLM provider configured for AGENTSPAN_LLM_MODEL -""" - -import json -import os -import re -import shutil -import subprocess -import textwrap -import time -import uuid -from pathlib import Path - -import pytest -import requests -from conftest import BASE_URL, CLI_PATH, MODEL, get_workflow - -pytestmark = [pytest.mark.e2e, pytest.mark.xdist_group("cli-skills")] - - -def _cli_server_url() -> str: - """Use IPv4 loopback for local CLI calls to avoid localhost resolving to ::1.""" - return BASE_URL.replace("http://localhost", "http://127.0.0.1") - - -@pytest.fixture() -def cli_path() -> str: - """Return a runnable agentspan CLI path or skip when it is not available.""" - candidate = Path(CLI_PATH).expanduser() - if candidate.parent != Path(".") or os.sep in CLI_PATH: - if candidate.exists(): - return str(candidate) - pytest.skip(f"AGENTSPAN_CLI_PATH not found: {CLI_PATH}") - - found = shutil.which(CLI_PATH) - if not found: - pytest.skip(f"agentspan CLI not found on PATH: {CLI_PATH}") - return found - - -@pytest.fixture() -def cli_skill_dir(tmp_path): - """Create a deterministic skill that must call a local script worker.""" - skill_name = f"cli_skill_e2e_{uuid.uuid4().hex[:8]}" - skill_dir = tmp_path / skill_name - skill_dir.mkdir() - - (skill_dir / "SKILL.md").write_text( - textwrap.dedent( - f"""\ - --- - name: {skill_name} - description: CLI skill e2e fixture. - --- - ## Workflow - If no prior tool result is available, call the {skill_name}__echo_args - tool exactly once. Pass the original user's request as the command argument. - - After the tool returns any line beginning with CLI_SKILL_ECHO:, produce - a final answer containing that exact line and do not call any tool again. - - If the current request is "Please continue where you left off.", do not - call a tool. Return the most recent CLI_SKILL_ECHO: line from the - conversation exactly. - """ - ) - ) - - (skill_dir / "alpha-agent.md").write_text("# Alpha Agent\nAnalyze the request.\n") - (skill_dir / "beta-agent.md").write_text("# Beta Agent\nSummarize the analysis.\n") - - references_dir = skill_dir / "references" - references_dir.mkdir() - (references_dir / "guide.md").write_text("# CLI_REFERENCE_GUIDE\nUse this guide.\n") - - scripts_dir = skill_dir / "scripts" - scripts_dir.mkdir() - echo_script = scripts_dir / "echo_args.py" - echo_script.write_text( - textwrap.dedent( - """\ - #!/usr/bin/env python3 - import sys - args = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else "no-args" - print(f"CLI_SKILL_ECHO:{args}") - """ - ) - ) - echo_script.chmod(0o755) - return skill_name, skill_dir - - -def _run_cli(cli_path: str, *args: str, timeout: int = 120) -> subprocess.CompletedProcess: - server_url = _cli_server_url() - env = {**os.environ, "AGENTSPAN_SERVER_URL": server_url} - return subprocess.run( - [cli_path, "--server", server_url, *args], - capture_output=True, - text=True, - timeout=timeout, - env=env, - ) - - -def _start_cli(cli_path: str, *args: str) -> subprocess.Popen: - server_url = _cli_server_url() - env = {**os.environ, "AGENTSPAN_SERVER_URL": server_url} - return subprocess.Popen( - [cli_path, "--server", server_url, *args], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - env=env, - ) - - -def _execution_id(output: str) -> str: - match = re.search(r"Execution:\s*([^\s)]+)", output) - assert match, f"could not find execution id in CLI output:\n{output}" - return match.group(1) - - -def _json_from_cli(output: str) -> dict: - start = output.find("{") - assert start >= 0, f"could not find JSON object in CLI output:\n{output}" - return json.loads(output[start:]) - - -def _wait_terminal(execution_id: str, timeout: int = 120) -> dict: - deadline = time.time() + timeout - last = {} - while time.time() < deadline: - last = get_workflow(execution_id) - status = last.get("status") - if status in {"COMPLETED", "FAILED", "TERMINATED", "TIMED_OUT"}: - return last - time.sleep(2) - pytest.fail(f"execution {execution_id} did not finish; last={last}") - - -def _assert_echo_worker_completed(workflow: dict, marker: str) -> None: - tasks = workflow.get("tasks", []) - scheduled = [ - (t.get("taskDefName"), t.get("referenceTaskName"), t.get("pollCount", 0)) - for t in tasks - if t.get("status") == "SCHEDULED" - ] - assert not scheduled, f"worker tasks stuck in SCHEDULED: {scheduled}" - - echo_tasks = [t for t in tasks if "echo_args" in t.get("taskDefName", "")] - task_names = [t.get("taskDefName") for t in tasks] - assert echo_tasks, f"echo_args was not invoked. Tasks: {task_names}" - assert all(t.get("status") == "COMPLETED" for t in echo_tasks), echo_tasks - assert any(marker in str(t.get("outputData", {})) for t in echo_tasks), [ - t.get("outputData", {}) for t in echo_tasks - ] - - -def _assert_loaded_skill_raw_config(output: str, skill_name: str) -> None: - data = json.loads(output) - assert skill_name in data.get("skillMd", "") - - agent_files = data.get("agentFiles", {}) - assert {"alpha", "beta"}.issubset(agent_files.keys()), agent_files - - scripts = data.get("scripts", {}) - assert "echo_args" in scripts, scripts - - resources = data.get("resourceFiles", []) - assert "references/guide.md" in resources, resources - - serialized = json.dumps(data) - assert "_skillPath" not in serialized - assert "_skillSections" not in serialized - - -def _write_registered_dependency_skills(tmp_path): - suffix = uuid.uuid4().hex[:8] - child_name = f"child-skill-{suffix}" - parent_name = f"parent-skill-{suffix}" - - child_v1 = tmp_path / f"{child_name}-v1" - child_v1.mkdir() - (child_v1 / "SKILL.md").write_text( - textwrap.dedent( - f"""\ - --- - name: {child_name} - description: Child skill v1. - --- - ## Workflow - Child dependency version one. - """ - ) - ) - scripts = child_v1 / "scripts" - scripts.mkdir() - script = scripts / "echo_args.py" - script.write_text( - "#!/usr/bin/env python3\nimport sys\nprint('CHILD_V1:' + ' '.join(sys.argv[1:]))\n" - ) - script.chmod(0o755) - refs = child_v1 / "references" - refs.mkdir() - (refs / "guide.md").write_text("CHILD_GUIDE_V1\n") - - parent = tmp_path / parent_name - parent.mkdir() - (parent / "SKILL.md").write_text( - textwrap.dedent( - f"""\ - --- - name: {parent_name} - description: Parent skill. - --- - ## Workflow - Use the {child_name} skill for the request. - """ - ) - ) - - child_v2 = tmp_path / f"{child_name}-v2" - child_v2.mkdir() - (child_v2 / "SKILL.md").write_text( - textwrap.dedent( - f"""\ - --- - name: {child_name} - description: Child skill v2. - --- - ## Workflow - Child dependency version two. - """ - ) - ) - - return parent_name, parent, child_name, child_v1, child_v2 - - -def _stop_process(proc: subprocess.Popen) -> None: - if proc.poll() is not None: - return - proc.terminate() - try: - proc.communicate(timeout=5) - except subprocess.TimeoutExpired: - proc.kill() - proc.communicate(timeout=5) - - -class TestSuite16CliSkills: - def test_cli_skill_register_list_get_pull_and_delete(self, cli_path, cli_skill_dir, tmp_path): - """Server registry lifecycle is deterministic and does not require an LLM call.""" - skill_name, skill_dir = cli_skill_dir - version = f"v-{uuid.uuid4().hex[:8]}" - - register = _run_cli( - cli_path, - "skill", - "register", - str(skill_dir), - "--version", - version, - "--model", - MODEL, - timeout=60, - ) - assert register.returncode == 0, f"stdout:\n{register.stdout}\nstderr:\n{register.stderr}" - detail = _json_from_cli(register.stdout) - assert detail["name"] == skill_name - assert detail["version"] == version - assert detail["status"] == "READY" - assert detail["rawConfig"]["scripts"]["echo_args"]["filename"] == "echo_args.py" - - listed = _run_cli(cli_path, "skill", "list", "--all-versions", timeout=60) - assert listed.returncode == 0, f"stdout:\n{listed.stdout}\nstderr:\n{listed.stderr}" - assert skill_name in listed.stdout - assert version[:12] in listed.stdout - - got = _run_cli(cli_path, "skill", "get", skill_name, "--version", version, timeout=60) - assert got.returncode == 0, f"stdout:\n{got.stdout}\nstderr:\n{got.stderr}" - got_detail = json.loads(got.stdout) - assert got_detail["checksum"] == detail["checksum"] - - pulled = tmp_path / "pulled-skill" - pull = _run_cli(cli_path, "skill", "pull", skill_name, str(pulled), "--version", version, timeout=60) - assert pull.returncode == 0, f"stdout:\n{pull.stdout}\nstderr:\n{pull.stderr}" - assert (pulled / "SKILL.md").exists() - assert (pulled / "references" / "guide.md").read_text() == "# CLI_REFERENCE_GUIDE\nUse this guide.\n" - - deleted = _run_cli(cli_path, "skill", "delete", skill_name, "--version", version, "--yes", timeout=60) - assert deleted.returncode == 0, f"stdout:\n{deleted.stdout}\nstderr:\n{deleted.stderr}" - - missing = _run_cli(cli_path, "skill", "get", skill_name, "--version", version, timeout=60) - assert missing.returncode != 0 - - def test_registered_cross_skill_dependency_versions_are_pinned(self, cli_path, tmp_path): - """Registered parent skills compile against dependency versions pinned at registration.""" - parent_name, parent_dir, child_name, child_v1, child_v2 = _write_registered_dependency_skills(tmp_path) - - child_v1_version = f"v1-{uuid.uuid4().hex[:8]}" - parent_version = f"v1-{uuid.uuid4().hex[:8]}" - child_v2_version = f"v2-{uuid.uuid4().hex[:8]}" - - child_register = _run_cli( - cli_path, - "skill", - "register", - str(child_v1), - "--version", - child_v1_version, - "--model", - MODEL, - timeout=60, - ) - assert child_register.returncode == 0, ( - f"stdout:\n{child_register.stdout}\nstderr:\n{child_register.stderr}" - ) - - parent_register = _run_cli( - cli_path, - "skill", - "register", - str(parent_dir), - "--version", - parent_version, - "--model", - MODEL, - timeout=60, - ) - assert parent_register.returncode == 0, ( - f"stdout:\n{parent_register.stdout}\nstderr:\n{parent_register.stderr}" - ) - parent_detail = _json_from_cli(parent_register.stdout) - pinned = parent_detail["rawConfig"]["crossSkillRefs"][child_name]["skillRef"] - assert pinned["version"] == child_v1_version - - child_v2_register = _run_cli( - cli_path, - "skill", - "register", - str(child_v2), - "--version", - child_v2_version, - "--model", - MODEL, - timeout=60, - ) - assert child_v2_register.returncode == 0, ( - f"stdout:\n{child_v2_register.stdout}\nstderr:\n{child_v2_register.stderr}" - ) - - compile_response = requests.post( - f"{BASE_URL}/api/agent/compile", - json={ - "framework": "skill", - "skillRef": { - "name": parent_name, - "version": parent_version, - "model": MODEL, - }, - }, - timeout=30, - ) - assert compile_response.status_code == 200, compile_response.text - compiled = compile_response.json() - agent_def = compiled["workflowDef"]["metadata"]["agentDef"] - child_ref = agent_def["crossSkillRefs"][child_name] - assert child_ref["skillRef"]["version"] == child_v1_version - assert "Child dependency version one" in child_ref["skillMd"] - assert "Child dependency version two" not in child_ref["skillMd"] - assert "echo_args" in child_ref["scripts"] - assert "references/guide.md" in child_ref["resourceFiles"] - - def test_cli_skill_run_registered_executes_downloaded_script_worker(self, cli_path, cli_skill_dir): - """`agentspan skill run ` downloads a registered skill and runs its script workers.""" - skill_name, skill_dir = cli_skill_dir - version = f"run-{uuid.uuid4().hex[:8]}" - - register = _run_cli( - cli_path, - "skill", - "register", - str(skill_dir), - "--version", - version, - "--model", - MODEL, - timeout=60, - ) - assert register.returncode == 0, f"stdout:\n{register.stdout}\nstderr:\n{register.stderr}" - - result = _run_cli( - cli_path, - "skill", - "run", - skill_name, - "registered_run_proof", - "--version", - version, - "--model", - MODEL, - "--timeout", - "120", - timeout=180, - ) - - assert result.returncode == 0, f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" - execution_id = _execution_id(result.stdout) - workflow = get_workflow(execution_id) - assert workflow.get("status") == "COMPLETED", workflow - _assert_echo_worker_completed(workflow, "CLI_SKILL_ECHO:") - - def test_cli_skill_run_ephemeral_executes_script_worker(self, cli_path, cli_skill_dir): - """`agentspan skill run` starts local workers and completes a real execution.""" - _skill_name, skill_dir = cli_skill_dir - - result = _run_cli( - cli_path, - "skill", - "run", - str(skill_dir), - "ephemeral_proof", - "--model", - MODEL, - "--timeout", - "120", - timeout=180, - ) - - assert result.returncode == 0, f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" - execution_id = _execution_id(result.stdout) - workflow = get_workflow(execution_id) - assert workflow.get("status") == "COMPLETED", workflow - _assert_echo_worker_completed(workflow, "CLI_SKILL_ECHO:") - - def test_cli_skill_load_serve_and_run_by_name(self, cli_path, cli_skill_dir): - """Production path: load deploys, serve polls workers, run --name starts framework skill.""" - skill_name, skill_dir = cli_skill_dir - - load = _run_cli( - cli_path, - "skill", - "load", - str(skill_dir), - "--model", - MODEL, - timeout=60, - ) - assert load.returncode == 0, f"stdout:\n{load.stdout}\nstderr:\n{load.stderr}" - assert skill_name in load.stdout - - loaded = _run_cli(cli_path, "agent", "get", skill_name, timeout=60) - assert loaded.returncode == 0, f"stdout:\n{loaded.stdout}\nstderr:\n{loaded.stderr}" - _assert_loaded_skill_raw_config(loaded.stdout, skill_name) - - serve = _start_cli(cli_path, "skill", "serve", str(skill_dir)) - try: - time.sleep(2) - if serve.poll() is not None: - stdout, stderr = serve.communicate(timeout=1) - pytest.fail(f"skill serve exited early\nstdout:\n{stdout}\nstderr:\n{stderr}") - - run = _run_cli( - cli_path, - "agent", - "run", - "--name", - skill_name, - "--no-stream", - "served_proof", - timeout=60, - ) - assert run.returncode == 0, f"stdout:\n{run.stdout}\nstderr:\n{run.stderr}" - execution_id = _execution_id(run.stdout) - workflow = _wait_terminal(execution_id) - assert workflow.get("status") == "COMPLETED", workflow - _assert_echo_worker_completed(workflow, "CLI_SKILL_ECHO:") - finally: - _stop_process(serve) diff --git a/e2e/test_suite1_basic_validation.py b/e2e/test_suite1_basic_validation.py index 3fd998d58..5cd90ddcc 100644 --- a/e2e/test_suite1_basic_validation.py +++ b/e2e/test_suite1_basic_validation.py @@ -206,7 +206,7 @@ def _assert_tool_in_agent_def( # ── LLM Judge ────────────────────────────────────────────────────────── -JUDGE_MODEL = os.environ.get("AGENTSPAN_JUDGE_MODEL", "claude-sonnet-4-6") +JUDGE_MODEL = os.environ.get("CONDUCTOR_AGENT_JUDGE_MODEL", "claude-sonnet-4-6") JUDGE_SYSTEM_PROMPT = """\ You are a strict validation judge for a workflow compilation system. @@ -396,7 +396,7 @@ def _judge_compiled_workflow(comparison_text: str) -> dict: """Call LLM to judge whether compiled workflow matches agent spec. Uses Anthropic (claude-*) or OpenAI (gpt-*) based on model name. - Defaults to Claude Sonnet. Configure via AGENTSPAN_JUDGE_MODEL env var. + Defaults to Claude Sonnet. Configure via CONDUCTOR_AGENT_JUDGE_MODEL env var. Returns dict with keys: pass (bool), missing (list), explanation (str). """ @@ -409,7 +409,7 @@ def _judge_compiled_workflow(comparison_text: str) -> dict: else: pytest.fail( f"Unknown judge model '{model}'. " - f"Set AGENTSPAN_JUDGE_MODEL to a claude-* or gpt-* model." + f"Set CONDUCTOR_AGENT_JUDGE_MODEL to a claude-* or gpt-* model." ) # Strip markdown code fences if present diff --git a/e2e/test_suite20_plan_execute.py b/e2e/test_suite20_plan_execute.py index f3c2389d7..58d1df20d 100644 --- a/e2e/test_suite20_plan_execute.py +++ b/e2e/test_suite20_plan_execute.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Suite 20: Plan-Execute (PAC/PAE) — workflow scheduling regression guard. Catches the conductor-side bug where ``subWorkflowParam.workflowDefinition`` @@ -35,9 +32,9 @@ pytestmark = pytest.mark.e2e -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") BASE_URL = SERVER_URL.rstrip("/").replace("/api", "") -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") PLAN_EXEC_TIMEOUT = 300 # 5 min — plan + compile + execute + (optional) fallback @@ -97,10 +94,10 @@ def test_plan_execute_submits_and_schedules(self, runtime, model): max_turns=3, instructions=( "Produce a JSON plan inside a ```json fence describing exactly one " - "step that calls the ``append_line`` tool with path='/tmp/agentspan_s20.txt' " + "step that calls the ``append_line`` tool with path='/tmp/conductor_agent_s20.txt' " "and line='hello'. Use this exact shape:\n" '```json\n{"steps": [{"tool": "append_line", ' - '"args": {"path": "/tmp/agentspan_s20.txt", "line": "hello"}}]}\n```' + '"args": {"path": "/tmp/conductor_agent_s20.txt", "line": "hello"}}]}\n```' ), ) @@ -123,7 +120,7 @@ def test_plan_execute_submits_and_schedules(self, runtime, model): ) result = runtime.run( - harness, "Append 'hello' to /tmp/agentspan_s20.txt", timeout=PLAN_EXEC_TIMEOUT + harness, "Append 'hello' to /tmp/conductor_agent_s20.txt", timeout=PLAN_EXEC_TIMEOUT ) assert result.execution_id, f"start failed; result={result!r}" @@ -399,7 +396,7 @@ class TestSuite20PlanExecuteWhitelist: """PAC/PAE tool whitelist enforcement. Verifies the security boundary at - ``server/src/main/java/dev/agentspan/runtime/service/PlanAndCompileTask.java:301``: + ``server/src/main/java/dev/conductor_agent/runtime/service/PlanAndCompileTask.java:301``: a plan ``op.tool`` not in the agent's declared ``tools`` list (plus the implicit ``llm_chat_complete`` builtin) is rejected at compile time. The compile-fail SWITCH then routes to the fallback agent (or @@ -678,7 +675,7 @@ def test_text_planner_context_appears_in_planner_prompt(self, runtime, model): ) result = runtime.run( - harness, "Append 'hi' to /tmp/agentspan_s20_ctx.txt", timeout=PLAN_EXEC_TIMEOUT + harness, "Append 'hi' to /tmp/conductor_agent_s20_ctx.txt", timeout=PLAN_EXEC_TIMEOUT ) assert result.execution_id, f"start failed; result={result!r}" assert str(result.status) in ( @@ -771,7 +768,7 @@ def test_no_planner_context_emits_no_ctx_build_task(self, runtime, model): ) result = runtime.run( - harness, "Append 'hi' to /tmp/agentspan_s20_noctx.txt", timeout=PLAN_EXEC_TIMEOUT + harness, "Append 'hi' to /tmp/conductor_agent_s20_noctx.txt", timeout=PLAN_EXEC_TIMEOUT ) assert result.execution_id, f"start failed; result={result!r}" diff --git a/e2e/test_suite21_scheduling.py b/e2e/test_suite21_scheduling.py index 3a3bb80b2..541c15cec 100644 --- a/e2e/test_suite21_scheduling.py +++ b/e2e/test_suite21_scheduling.py @@ -11,7 +11,7 @@ Targets the scheduler-capable Conductor at ``SCHEDULER_CONDUCTOR_URL`` (default ``http://localhost:8089/api``). Skipped automatically if the -scheduler endpoint isn't available — this is the agentspan-runtime case +scheduler endpoint isn't available — this is the Conductor Agents runtime case where the embedded Conductor lacks the scheduler module. No LLM calls — the scheduled "agent" is a bare no-op Conductor workflow. @@ -85,7 +85,7 @@ def agent_name(conductor_clients) -> Iterator[str]: "name": name, "version": 1, "description": "Scheduling e2e no-op workflow", - "ownerEmail": "e2e@agentspan.test", + "ownerEmail": "e2e@conductor_agent.test", "schemaVersion": 2, "timeoutSeconds": 60, "timeoutPolicy": "TIME_OUT_WF", diff --git a/e2e/test_suite22_ocg.py b/e2e/test_suite22_ocg.py index 0fe044e26..1d0a7c046 100644 --- a/e2e/test_suite22_ocg.py +++ b/e2e/test_suite22_ocg.py @@ -10,7 +10,7 @@ 3. Negative: agent with no OCG tools → no traffic on either stub Manages two stub OCG instances on dedicated ports. -No mocks of agentspan itself. Real server, real LLM, stub OCG backends. +No mocks of Conductor Agents itself. Real server, real LLM, stub OCG backends. """ import json @@ -34,9 +34,9 @@ CA_PORT = 3062 TIMEOUT = 120 -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") -# The agentspan server resolves the per-tool OCG URL server-side, so the +# The Conductor Agents server resolves the per-tool OCG URL server-side, so the # stubs must be reachable from the server process — localhost works for the # local e2e topology (server and tests on the same host). US_URL = f"http://localhost:{US_PORT}" diff --git a/e2e/test_suite23_from_instance_and_event_hitl.py b/e2e/test_suite23_from_instance_and_event_hitl.py index e53529ac9..8c1988eea 100644 --- a/e2e/test_suite23_from_instance_and_event_hitl.py +++ b/e2e/test_suite23_from_instance_and_event_hitl.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Suite 23: Feature-parity gaps with the Java reference SDK. Two independent features: diff --git a/e2e/test_suite24_agent_client.py b/e2e/test_suite24_agent_client.py index cc996329c..7bbfeea85 100644 --- a/e2e/test_suite24_agent_client.py +++ b/e2e/test_suite24_agent_client.py @@ -17,7 +17,7 @@ The scheduled "agent" target is a bare no-op Conductor workflow so no LLM is invoked for the schedule tests. -Targets the live Agentspan server (``AGENTSPAN_SERVER_URL``). The schedule +Targets the live Conductor Agents server (``CONDUCTOR_SERVER_URL``). The schedule tests are skipped automatically if the server's Conductor lacks the scheduler module. """ @@ -36,8 +36,8 @@ pytestmark = [pytest.mark.e2e] -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") -_API = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api").rstrip("/") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") +_API = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api").rstrip("/") def _scheduler_available() -> bool: @@ -100,7 +100,7 @@ def noop_agent_name(self): "name": name, "version": 1, "description": "AgentClient schedule e2e no-op workflow", - "ownerEmail": "e2e@agentspan.test", + "ownerEmail": "e2e@conductor_agent.test", "schemaVersion": 2, "timeoutSeconds": 60, "timeoutPolicy": "TIME_OUT_WF", diff --git a/e2e/test_suite25_media_input.py b/e2e/test_suite25_media_input.py index 3512ff1b9..afbe5b834 100644 --- a/e2e/test_suite25_media_input.py +++ b/e2e/test_suite25_media_input.py @@ -16,9 +16,9 @@ rejects data URIs, so the test writes those bytes to a file and passes its path. The server only reads files under its allowed media directory, which defaults to ``~/worker-payload/`` (the directory used when ``conductor.file-storage.parentDir`` is unset — the default -``agentspan server start`` config). This assumes the server runs on the same +``Conductor Agents server start`` config). This assumes the server runs on the same host as the test — the standard local / bundle e2e setup. Set -``AGENTSPAN_MEDIA_DIR`` to override the directory for deployments that +``CONDUCTOR_AGENT_MEDIA_DIR`` to override the directory for deployments that configure a custom allowed media dir. Parametrized across providers. The Anthropic positive case is ``skip``ped: in @@ -130,9 +130,9 @@ def _require_key(key_env: str): # defaults to ``~/worker-payload/`` on the server's host (see DocumentAccessPolicy; # used when ``conductor.file-storage.parentDir`` is unset). Deployments that # configure a different allowed dir (e.g. a custom ``file-storage.parentDir``) -# can point the test at it via ``AGENTSPAN_MEDIA_DIR``. +# can point the test at it via ``CONDUCTOR_AGENT_MEDIA_DIR``. _ALLOWED_MEDIA_DIR = Path( - os.environ.get("AGENTSPAN_MEDIA_DIR") or (Path(os.path.expanduser("~")) / "worker-payload") + os.environ.get("CONDUCTOR_AGENT_MEDIA_DIR") or (Path(os.path.expanduser("~")) / "worker-payload") ) diff --git a/e2e/test_suite26_worker_credentials.py b/e2e/test_suite26_worker_credentials.py index d842abcf8..83f6fc862 100644 --- a/e2e/test_suite26_worker_credentials.py +++ b/e2e/test_suite26_worker_credentials.py @@ -16,7 +16,7 @@ CLI dependency). Tool outputs are asserted from the workflow task's ``outputData`` (not LLM phrasing) for determinism. -Targets the live Agentspan server (``AGENTSPAN_SERVER_URL``). +Targets the live Conductor Agents server (``CONDUCTOR_SERVER_URL``). """ from __future__ import annotations @@ -35,7 +35,7 @@ CRED_NAME = "E2E_WORKER_SECRET" CRED_VALUE = f"s3cr3t-{uuid.uuid4().hex[:12]}" -_API = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api").rstrip("/") +_API = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api").rstrip("/") _BASE = _API.replace("/api", "") diff --git a/e2e/test_suite2_tool_calling.py b/e2e/test_suite2_tool_calling.py index 68d66c316..a641945f5 100644 --- a/e2e/test_suite2_tool_calling.py +++ b/e2e/test_suite2_tool_calling.py @@ -94,7 +94,7 @@ def _make_agent(model: str) -> Agent: def _get_workflow(execution_id: str) -> dict: """Fetch workflow from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() @@ -199,7 +199,7 @@ def _credential_audit(agent: Agent) -> str: Returns a human-readable report showing which credentials are required and which are missing from the server. """ - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") # Fetch stored credentials from server @@ -291,215 +291,6 @@ def _get_output_text(result) -> str: # ── Test ──────────────────────────────────────────────────────────────── -@pytest.mark.timeout(300) -class TestSuite2ToolCalling: - """Credential lifecycle: missing -> env ignored -> add -> update.""" - - @pytest.mark.usefixtures("requires_runtime_metadata") - def test_credential_lifecycle(self, runtime, cli_credentials, model): - """Full credential lifecycle test — sequential steps with cleanup.""" - try: - self._run_lifecycle(runtime, cli_credentials, model) - finally: - # Always clean up credentials - cli_credentials.delete(CRED_A) - cli_credentials.delete(CRED_B) - # Clean env vars if they leaked - os.environ.pop(CRED_A, None) - os.environ.pop(CRED_B, None) - - def _run_lifecycle(self, runtime, cli_credentials, model): - agent = _make_agent(model) - owned_runtimes: list[AgentRuntime] = [] - - def restart_runtime(current: AgentRuntime) -> AgentRuntime: - current.shutdown() - # Let old poll loops drain before new workers start with fresh - # execution tokens for the updated credential state. - time.sleep(2) - fresh = AgentRuntime() - owned_runtimes.append(fresh) - return fresh - - try: - # ── Step 1: Clean slate ───────────────────────────────────── - cli_credentials.delete(CRED_A) - cli_credentials.delete(CRED_B) - - # ── Step 2: No credentials — paid tools should fail ───────── - result = runtime.run(agent, "Call all three tools.", timeout=TIMEOUT) - - assert result.execution_id, ( - f"[Step 2: No credentials] No execution_id returned. " - f"{_run_diagnostic(result)}" - ) - - # The run should reach a terminal state (COMPLETED or FAILED). - # Paid tools should raise RuntimeError because credentials are missing. - assert result.status in ("COMPLETED", "FAILED", "TERMINATED"), ( - f"[Step 2: No credentials] Expected terminal status, " - f"got '{result.status}'. The agent should either complete " - f"(reporting tool errors) or fail outright when credentials " - f"are missing.\n" - f" {_run_diagnostic(result)}\n" - f" {_tool_diagnostics(result.execution_id)}" - ) - - # Verify via workflow tasks: paid tools must be terminal (not retryable). - # Conductor maps TaskResult.FAILED_WITH_TERMINAL_ERROR → Task.COMPLETED_WITH_ERRORS - tool_tasks_s2 = _find_tool_tasks_for(result.execution_id) - terminal_statuses = {"FAILED_WITH_TERMINAL_ERROR", "COMPLETED_WITH_ERRORS"} - for paid in ("paid_tool_a", "paid_tool_b"): - if paid in tool_tasks_s2: - task_info = tool_tasks_s2[paid] - assert task_info["status"] in terminal_statuses, ( - f"[Step 2: No credentials] {paid} should be terminal " - f"(not retryable), got '{task_info['status']}'. Missing " - f"credentials are a config issue — retries are pointless.\n" - f" task={task_info}" - ) - - # ── Step 3: Env vars should NOT be read ───────────────────── - os.environ[CRED_A] = "from-env-aaa" - os.environ[CRED_B] = "from-env-bbb" - try: - result_env = runtime.run( - agent, "Call all three tools.", timeout=TIMEOUT - ) - - # The paid tools should STILL fail despite env vars being set. - # The SDK resolves credentials from the server, not env. - output_env = _get_output_text(result_env) - - # Check for "from-env" (unique prefix of our test env values). - # Using "fro" caused false positives when LLM prose contained - # "from" in normal words. - assert "from-env" not in output_env, ( - "SECURITY VIOLATION: env vars were read for credential " - "resolution! The SDK MUST NOT resolve credentials from " - "environment variables — only from the server.\n" - f" {_run_diagnostic(result_env)}\n" - f" output_text={output_env[:300]}" - ) - finally: - os.environ.pop(CRED_A, None) - os.environ.pop(CRED_B, None) - - # ── Step 4: Add credentials via CLI ───────────────────────── - runtime = restart_runtime(runtime) - cli_credentials.set(CRED_A, "secret-aaa-value") - cli_credentials.set(CRED_B, "secret-bbb-value") - - result_with_creds = runtime.run( - agent, "Call all three tools.", timeout=TIMEOUT - ) - _assert_run_completed(result_with_creds, "Step 4: With credentials", agent) - - # Primary: validate via workflow task data - tool_tasks_s4 = _find_tool_tasks_for(result_with_creds.execution_id) - - assert "free_tool" in tool_tasks_s4, ( - f"[Step 4] free_tool task not found in workflow.\n" - f" found_tasks={list(tool_tasks_s4.keys())}" - ) - assert tool_tasks_s4["free_tool"]["status"] == "COMPLETED", ( - f"[Step 4] free_tool not COMPLETED.\n" - f" task={tool_tasks_s4['free_tool']}" - ) - - assert "paid_tool_a" in tool_tasks_s4, ( - f"[Step 4] paid_tool_a task not found in workflow.\n" - f" found_tasks={list(tool_tasks_s4.keys())}" - ) - assert tool_tasks_s4["paid_tool_a"]["status"] == "COMPLETED", ( - f"[Step 4] paid_tool_a not COMPLETED.\n" - f" task={tool_tasks_s4['paid_tool_a']}" - ) - s4_paid_a_output = str(tool_tasks_s4["paid_tool_a"]["output"]) - assert "sec" in s4_paid_a_output, ( - f"[Step 4] paid_tool_a output should contain 'sec' " - f"(first 3 chars of 'secret-aaa-value').\n" - f" task_output={s4_paid_a_output}" - ) - - assert "paid_tool_b" in tool_tasks_s4, ( - f"[Step 4] paid_tool_b task not found in workflow.\n" - f" found_tasks={list(tool_tasks_s4.keys())}" - ) - assert tool_tasks_s4["paid_tool_b"]["status"] == "COMPLETED", ( - f"[Step 4] paid_tool_b not COMPLETED.\n" - f" task={tool_tasks_s4['paid_tool_b']}" - ) - s4_paid_b_output = str(tool_tasks_s4["paid_tool_b"]["output"]) - assert "sec" in s4_paid_b_output, ( - f"[Step 4] paid_tool_b output should contain 'sec' " - f"(first 3 chars of 'secret-bbb-value').\n" - f" task_output={s4_paid_b_output}" - ) - - # Secondary: also check LLM output text - output_creds = _get_output_text(result_with_creds) - - assert "free" in output_creds.lower(), ( - f"[Step 4: With credentials] free_tool output not found in " - f"agent response. free_tool always returns 'free:ok' — if " - f"missing, the agent may not have called it.\n" - f" {_run_diagnostic(result_with_creds)}\n" - f" output_text={output_creds[:300]}\n" - f" {_tool_diagnostics(result_with_creds.execution_id)}" - ) - assert "sec" in output_creds, ( - f"[Step 4: With credentials] paid_tool_a should return 'sec' " - f"(first 3 chars of 'secret-aaa-value'). If missing, credential " - f"'{CRED_A}' may not have been resolved correctly.\n" - f" {_run_diagnostic(result_with_creds)}\n" - f" output_text={output_creds[:300]}\n" - f" {_tool_diagnostics(result_with_creds.execution_id)}" - ) - - # ── Step 5: Update credentials via CLI ────────────────────── - runtime = restart_runtime(runtime) - cli_credentials.set(CRED_A, "newval-xxx-updated") - cli_credentials.set(CRED_B, "newval-yyy-updated") - - result_updated = runtime.run( - agent, "Call all three tools.", timeout=TIMEOUT - ) - _assert_run_completed(result_updated, "Step 5: Updated credentials", agent) - - # Primary: validate via workflow task data - tool_tasks_s5 = _find_tool_tasks_for(result_updated.execution_id) - - assert "paid_tool_a" in tool_tasks_s5, ( - f"[Step 5] paid_tool_a task not found in workflow.\n" - f" found_tasks={list(tool_tasks_s5.keys())}" - ) - assert tool_tasks_s5["paid_tool_a"]["status"] == "COMPLETED", ( - f"[Step 5] paid_tool_a not COMPLETED.\n" - f" task={tool_tasks_s5['paid_tool_a']}" - ) - s5_paid_a_output = str(tool_tasks_s5["paid_tool_a"]["output"]) - assert "new" in s5_paid_a_output, ( - f"[Step 5] paid_tool_a output should contain 'new' " - f"(first 3 chars of 'newval-xxx-updated').\n" - f" task_output={s5_paid_a_output}" - ) - - # Secondary: also check LLM output text - output_updated = _get_output_text(result_updated) - - assert "new" in output_updated, ( - f"[Step 5: Updated credentials] paid_tool_a should return 'new' " - f"(first 3 chars of 'newval-xxx-updated'). If missing, the " - f"credential update via CLI may not have propagated.\n" - f" {_run_diagnostic(result_updated)}\n" - f" output_text={output_updated[:300]}\n" - f" {_tool_diagnostics(result_updated.execution_id)}" - ) - finally: - for owned in reversed(owned_runtimes): - owned.shutdown() - # Output masking (Audit gap D) is covered deterministically by the server's # SecretMaskingIntegrationTest (MockMvc + @MockBean AgentService). An e2e diff --git a/e2e/test_suite3_cli_tools.py b/e2e/test_suite3_cli_tools.py index 76291848b..1c3821ad3 100644 --- a/e2e/test_suite3_cli_tools.py +++ b/e2e/test_suite3_cli_tools.py @@ -166,7 +166,7 @@ def _run_diagnostic(result) -> str: def _get_workflow(execution_id: str) -> dict: """Fetch workflow from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() @@ -231,174 +231,3 @@ def _assert_run_completed(result, step_name: str): f"[{step_name}] Run did not complete. {diag}\n" f" {_tool_diagnostics(result.execution_id, {'cli_ls', 'cli_mktemp', 'cli_gh'})}" ) - - -# ── Test ──────────────────────────────────────────────────────────────── - - -@pytest.mark.timeout(600) -class TestSuite3CliTools: - """CLI tools: credential lifecycle + command whitelist.""" - - @pytest.mark.usefixtures("requires_runtime_metadata") - def test_cli_credential_lifecycle(self, runtime, cli_credentials, model): - """Full CLI credential lifecycle — sequential steps with cleanup.""" - real_token = os.environ.get("GITHUB_TOKEN") - if not real_token: - pytest.skip( - "GITHUB_TOKEN not set in environment — " - "required for Suite 3 CLI tools test" - ) - - # Verify gh CLI is installed - try: - subprocess.run( - ["gh", "--version"], capture_output=True, text=True, timeout=5 - ) - except FileNotFoundError: - pytest.skip("gh CLI not installed — required for Suite 3 CLI tools test") - - try: - self._run_lifecycle(runtime, cli_credentials, model, real_token) - finally: - cli_credentials.delete(CRED_NAME) - os.environ.pop(CRED_NAME, None) - - def _run_lifecycle(self, runtime, cli_credentials, model, real_token): - agent = _make_agent(model) - - # ── Step 1: Clean slate — remove credential from server ───── - cli_credentials.delete(CRED_NAME) - - # ── Step 2: Export GITHUB_TOKEN to env ────────────────────── - # This validates the SDK does NOT read credentials from env. - # The real token is in the env but NOT in the server store. - os.environ["GITHUB_TOKEN"] = real_token - - # ── Step 3: Run agent — ls/mktemp succeed, gh fails ──────── - result = runtime.run(agent, PROMPT_ALL_THREE, timeout=TIMEOUT) - - assert result.execution_id, ( - f"[Step 3: No credential] No execution_id. " - f"{_run_diagnostic(result)}" - ) - assert result.status in ("COMPLETED", "FAILED", "TERMINATED"), ( - f"[Step 3: No credential] Expected terminal status, " - f"got '{result.status}'. The agent should complete or fail " - f"when gh credential is missing.\n" - f" {_run_diagnostic(result)}\n" - f" {_tool_diagnostics(result.execution_id, {'cli_ls', 'cli_mktemp', 'cli_gh'})}" - ) - - output = _get_output_text(result) - - # ls and mktemp should succeed (no credentials needed) - assert "ls_ok" in output, ( - f"[Step 3: No credential] cli_ls should succeed — it needs no " - f"credentials.\n" - f" output={output[:500]}\n" - f" {_run_diagnostic(result)}\n" - f" {_tool_diagnostics(result.execution_id, {'cli_ls', 'cli_mktemp', 'cli_gh'})}" - ) - assert "mktemp_ok" in output, ( - f"[Step 3: No credential] cli_mktemp should succeed — it needs " - f"no credentials.\n" - f" output={output[:500]}\n" - f" {_run_diagnostic(result)}" - ) - - # gh should fail — credential not in server, env must NOT be used - assert "gh_ok" not in output, ( - f"[Step 3: No credential] SECURITY: cli_gh should NOT succeed — " - f"GITHUB_TOKEN is in env but NOT in the server credential store. " - f"If it succeeded, env vars are leaking through credential " - f"isolation.\n" - f" output={output[:500]}" - ) - - # ── Step 4: Add credential via CLI ────────────────────────── - cli_credentials.set(CRED_NAME, real_token) - - # ── Step 5: Run agent — all three should succeed ──────────── - result = runtime.run(agent, PROMPT_ALL_THREE, timeout=TIMEOUT) - _assert_run_completed(result, "Step 5: With credential") - - output = _get_output_text(result) - - assert "ls_ok" in output, ( - f"[Step 5: With credential] cli_ls should succeed.\n" - f" output={output[:500]}\n" - f" {_run_diagnostic(result)}" - ) - assert "mktemp_ok" in output, ( - f"[Step 5: With credential] cli_mktemp should succeed.\n" - f" output={output[:500]}\n" - f" {_run_diagnostic(result)}" - ) - assert "gh_ok" in output, ( - f"[Step 5: With credential] cli_gh should succeed — " - f"GITHUB_TOKEN was added to server credential store.\n" - f" output={output[:500]}\n" - f" {_run_diagnostic(result)}\n" - f" {_tool_diagnostics(result.execution_id, {'cli_ls', 'cli_mktemp', 'cli_gh'})}" - ) - - # ── Step 6: cd command — not allowed ───────────────────────── - # All validation is algorithmic — no LLM output parsing. - - EXPECTED_ALLOWED = ["ls", "mktemp", "gh"] - whitelist_agent = _make_whitelist_agent(model) - - # 6a. Validate whitelist via plan() — the compiled tool description - # must list exactly the expected allowed commands. - plan = runtime.plan(whitelist_agent) - ad = plan["workflowDef"]["metadata"]["agentDef"] - cli_tool = next( - (t for t in ad.get("tools", []) if "run_command" in t["name"]), - None, - ) - assert cli_tool is not None, ( - f"[Step 6: cd blocked] No run_command tool in compiled agent. " - f"Tools: {[t['name'] for t in ad.get('tools', [])]}" - ) - # Parse the exact allowed commands from the tool description. - # Format: "... Allowed commands: gh, ls, mktemp. ..." - tool_desc = cli_tool.get("description", "") - match = re.search(r"Allowed commands:\s*(.+?)\.", tool_desc) - assert match, ( - f"[Step 6: cd blocked] Could not find 'Allowed commands:' in " - f"compiled run_command tool description.\n" - f" description={tool_desc}" - ) - actual_commands = sorted(c.strip() for c in match.group(1).split(",")) - assert actual_commands == sorted(EXPECTED_ALLOWED), ( - f"[Step 6: cd blocked] Allowed commands mismatch.\n" - f" expected={sorted(EXPECTED_ALLOWED)}\n" - f" actual={actual_commands}" - ) - - # 6b. Validate cd rejection directly — call the validation function - # and assert it raises ValueError with the correct message. - with pytest.raises(ValueError, match="not allowed") as exc_info: - _validate_cli_command("cd", EXPECTED_ALLOWED) - - error_msg = str(exc_info.value) - for cmd in EXPECTED_ALLOWED: - assert cmd in error_msg, ( - f"[Step 6: cd blocked] Rejection error must list '{cmd}' " - f"as an allowed command.\n" - f" error_msg={error_msg}" - ) - - # 6c. Run the agent to verify it reaches terminal status. - result_cd = runtime.run(whitelist_agent, PROMPT_CD, timeout=TIMEOUT) - - assert result_cd.execution_id, ( - f"[Step 6: cd blocked] No execution_id. " - f"{_run_diagnostic(result_cd)}" - ) - assert result_cd.status in ("COMPLETED", "FAILED", "TERMINATED"), ( - f"[Step 6: cd blocked] Expected terminal status, " - f"got '{result_cd.status}'.\n" - f" {_run_diagnostic(result_cd)}" - ) diff --git a/e2e/test_suite4_mcp_tools.py b/e2e/test_suite4_mcp_tools.py index 93ed9ca41..4bcdfd51f 100644 --- a/e2e/test_suite4_mcp_tools.py +++ b/e2e/test_suite4_mcp_tools.py @@ -181,7 +181,7 @@ def _make_auth_agent(model, server_url, cred_name): def _get_workflow(execution_id): """Fetch workflow from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() @@ -355,103 +355,3 @@ def _validate_tool_execution(result, step_name): f"expected value '{expected}'.\n" f" output={output_str[:300]}" ) - - -# ── Test ───────────────────────────────────────────────────────────────── - - -@pytest.mark.timeout(600) -class TestSuite4McpTools: - """MCP tools: discovery, execution, and authenticated access.""" - - def test_mcp_lifecycle(self, runtime, cli_credentials, model): - """Full MCP lifecycle — unauthenticated → authenticated.""" - # Verify mcp-testkit is installed - try: - subprocess.run( - ["mcp-testkit", "--help"], - capture_output=True, - text=True, - timeout=5, - ) - except FileNotFoundError: - pytest.skip( - "mcp-testkit not installed — required for Suite 4 MCP tools test" - ) - - server_proc = None - try: - self._run_lifecycle(runtime, cli_credentials, model) - finally: - cli_credentials.delete(CRED_NAME) - - def _run_lifecycle(self, runtime, cli_credentials, model): - server_proc = None - try: - # ── Phase 1: Unauthenticated ────────────────────────────── - - # Step d: Start MCP server without auth - server_proc = _start_mcp_server(MCP_PORT) - - # Step e: Discover tools, validate all are present - discovered = _discover_tools_via_mcp(MCP_SERVER_URL) - assert len(discovered) == EXPECTED_TOOL_COUNT, ( - f"[Phase 1: Discovery] Expected {EXPECTED_TOOL_COUNT} tools, " - f"discovered {len(discovered)}.\n" - f" Missing: {sorted(set(EXPECTED_TOOL_NAMES) - set(discovered))}\n" - f" Extra: {sorted(set(discovered) - set(EXPECTED_TOOL_NAMES))}" - ) - assert set(discovered) == set(EXPECTED_TOOL_NAMES), ( - f"[Phase 1: Discovery] Tool names mismatch.\n" - f" Missing: {sorted(set(EXPECTED_TOOL_NAMES) - set(discovered))}\n" - f" Extra: {sorted(set(discovered) - set(EXPECTED_TOOL_NAMES))}" - ) - - # Steps b+c+f: Create agent, run with 3 tools, validate - agent = _make_agent(model, MCP_SERVER_URL) - result = runtime.run(agent, PROMPT_USE_3_TOOLS, timeout=TIMEOUT) - _validate_tool_execution(result, "Phase 1: Unauthenticated execution") - - # ── Phase 2: Authenticated ──────────────────────────────── - - # Step g: Stop server, restart with auth - _stop_mcp_server(server_proc) - server_proc = None - time.sleep(1) # Let port release - server_proc = _start_mcp_server(MCP_PORT, auth_key=MCP_AUTH_KEY) - - # Verify auth is enforced — unauthenticated call should fail - with pytest.raises(Exception): - _discover_tools_via_mcp(MCP_SERVER_URL) - - # Step h: Create auth agent with credential placeholder - auth_agent = _make_auth_agent(model, MCP_SERVER_URL, CRED_NAME) - - # Step i: Set credential via CLI - cli_credentials.set(CRED_NAME, MCP_AUTH_KEY) - - # Step j: Discover tools with auth, validate all present - discovered_auth = _discover_tools_via_mcp( - MCP_SERVER_URL, auth_key=MCP_AUTH_KEY - ) - assert len(discovered_auth) == EXPECTED_TOOL_COUNT, ( - f"[Phase 2: Auth Discovery] Expected {EXPECTED_TOOL_COUNT} tools, " - f"discovered {len(discovered_auth)}." - ) - assert set(discovered_auth) == set(EXPECTED_TOOL_NAMES), ( - f"[Phase 2: Auth Discovery] Tool names mismatch.\n" - f" Missing: {sorted(set(EXPECTED_TOOL_NAMES) - set(discovered_auth))}\n" - f" Extra: {sorted(set(discovered_auth) - set(EXPECTED_TOOL_NAMES))}" - ) - - # Step k: Execute and validate - result_auth = runtime.run( - auth_agent, PROMPT_USE_3_TOOLS, timeout=TIMEOUT - ) - _validate_tool_execution( - result_auth, "Phase 2: Authenticated execution" - ) - - finally: - if server_proc: - _stop_mcp_server(server_proc) diff --git a/e2e/test_suite5_http_tools.py b/e2e/test_suite5_http_tools.py index ab11fde74..cbabe799e 100644 --- a/e2e/test_suite5_http_tools.py +++ b/e2e/test_suite5_http_tools.py @@ -233,7 +233,7 @@ def _make_orkes_agent(model): def _get_workflow(execution_id): """Fetch workflow from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() @@ -450,100 +450,6 @@ def _validate_tool_execution(result, step_name): class TestSuite5HttpTools: """HTTP tools: API discovery, execution, and authenticated access.""" - def test_http_lifecycle(self, runtime, cli_credentials, model): - """Full HTTP lifecycle — unauthenticated → authenticated.""" - try: - subprocess.run( - ["mcp-testkit", "--help"], - capture_output=True, - text=True, - timeout=15, - ) - except (FileNotFoundError, subprocess.TimeoutExpired): - pytest.skip( - "mcp-testkit not installed or unresponsive — required for " - "Suite 5 HTTP tools test" - ) - - server_proc = None - try: - self._run_lifecycle(runtime, cli_credentials, model) - finally: - cli_credentials.delete(CRED_NAME) - - def _run_lifecycle(self, runtime, cli_credentials, model): - server_proc = None - try: - # ── Phase 1: Unauthenticated ────────────────────────────── - - # Step d: Start HTTP server without auth - server_proc = _start_http_server(HTTP_PORT) - - # Step e: Discover tools via OpenAPI spec, validate all present - discovered = _discover_tools_via_openapi(HTTP_SPEC_URL) - assert len(discovered) == EXPECTED_TOOL_COUNT, ( - f"[Phase 1: Discovery] Expected {EXPECTED_TOOL_COUNT} tools, " - f"discovered {len(discovered)}.\n" - f" Missing: {sorted(set(EXPECTED_TOOL_NAMES) - set(discovered))}\n" - f" Extra: {sorted(set(discovered) - set(EXPECTED_TOOL_NAMES))}" - ) - assert set(discovered) == set(EXPECTED_TOOL_NAMES), ( - f"[Phase 1: Discovery] Tool names mismatch.\n" - f" Missing: {sorted(set(EXPECTED_TOOL_NAMES) - set(discovered))}\n" - f" Extra: {sorted(set(discovered) - set(EXPECTED_TOOL_NAMES))}" - ) - - # Steps b+c+f: Create agent, run with 3 tools, validate - agent = _make_agent(model, HTTP_BASE_URL) - result = runtime.run(agent, PROMPT_USE_3_TOOLS, timeout=TIMEOUT) - _validate_tool_execution(result, "Phase 1: Unauthenticated execution") - - # ── Phase 2: Authenticated ──────────────────────────────── - - # Step g: Stop server, restart with auth - _stop_http_server(server_proc) - server_proc = None - time.sleep(1) # Let port release - server_proc = _start_http_server(HTTP_PORT, auth_key=HTTP_AUTH_KEY) - - # Verify auth is enforced — unauthenticated spec fetch should fail - unauth_resp = requests.get(HTTP_SPEC_URL, timeout=5) - assert unauth_resp.status_code in (401, 403), ( - f"[Phase 2: Auth check] Expected 401/403 without auth, " - f"got {unauth_resp.status_code}" - ) - - # Step h: Create auth agent with credential placeholder - auth_agent = _make_auth_agent(model, HTTP_BASE_URL, CRED_NAME) - - # Step i: Set credential via CLI - cli_credentials.set(CRED_NAME, HTTP_AUTH_KEY) - - # Step j: Discover tools with auth, validate all present - discovered_auth = _discover_tools_via_openapi( - HTTP_SPEC_URL, auth_key=HTTP_AUTH_KEY - ) - assert len(discovered_auth) == EXPECTED_TOOL_COUNT, ( - f"[Phase 2: Auth Discovery] Expected {EXPECTED_TOOL_COUNT} tools, " - f"discovered {len(discovered_auth)}." - ) - assert set(discovered_auth) == set(EXPECTED_TOOL_NAMES), ( - f"[Phase 2: Auth Discovery] Tool names mismatch.\n" - f" Missing: {sorted(set(EXPECTED_TOOL_NAMES) - set(discovered_auth))}\n" - f" Extra: {sorted(set(discovered_auth) - set(EXPECTED_TOOL_NAMES))}" - ) - - # Step k: Execute and validate - result_auth = runtime.run( - auth_agent, PROMPT_USE_3_TOOLS, timeout=TIMEOUT - ) - _validate_tool_execution( - result_auth, "Phase 2: Authenticated execution" - ) - - finally: - if server_proc: - _stop_http_server(server_proc) def test_external_openapi_spec(self, runtime, model): """External OpenAPI spec — validate startWorkflow discovery (steps l-n). diff --git a/e2e/test_suite6_pdf_tools.py b/e2e/test_suite6_pdf_tools.py index 7892df706..b4e177ea4 100644 --- a/e2e/test_suite6_pdf_tools.py +++ b/e2e/test_suite6_pdf_tools.py @@ -30,7 +30,7 @@ # ── Sample Markdown ────────────────────────────────────────────────────── SAMPLE_MARKDOWN = """\ -# Agentspan E2E Test Report +# Conductor Agents E2E Test Report ## Overview @@ -70,7 +70,7 @@ # Key phrases that MUST survive the markdown → PDF → markdown round trip. # These are content-level checks, not formatting checks. EXPECTED_PHRASES = [ - "Agentspan E2E Test Report", + "Conductor Agents E2E Test Report", "Overview", "Key Metrics", "Tests Run", @@ -103,7 +103,7 @@ def _make_agent(model): def _get_workflow(execution_id): """Fetch workflow from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() @@ -250,7 +250,7 @@ def test_pdf_generation_and_roundtrip(self, runtime, model): ) # Download PDF - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") if pdf_url.startswith("/"): pdf_url = f"{base_url}{pdf_url}" diff --git a/e2e/test_suite7_media_tools.py b/e2e/test_suite7_media_tools.py index d85887bb2..861f028a1 100644 --- a/e2e/test_suite7_media_tools.py +++ b/e2e/test_suite7_media_tools.py @@ -27,7 +27,7 @@ def _get_workflow(execution_id): """Fetch workflow from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() diff --git a/e2e/test_suite8_guardrails.py b/e2e/test_suite8_guardrails.py index 68a3d0539..ae48aaf1e 100644 --- a/e2e/test_suite8_guardrails.py +++ b/e2e/test_suite8_guardrails.py @@ -243,7 +243,7 @@ def _agent_with_strict_tool(model): def _get_workflow(execution_id): - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() diff --git a/e2e/test_suite9_handoffs.py b/e2e/test_suite9_handoffs.py index 8fa51d953..9508ea83a 100644 --- a/e2e/test_suite9_handoffs.py +++ b/e2e/test_suite9_handoffs.py @@ -110,7 +110,7 @@ def _data_agent(model): def _get_workflow(execution_id): """Fetch workflow execution from server API.""" - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") resp = requests.get(f"{base_url}/api/workflow/{execution_id}", timeout=10) resp.raise_for_status() diff --git a/examples/agents/01_basic_agent.py b/examples/agents/01_basic_agent.py index dfbd8e65f..900a33c12 100644 --- a/examples/agents/01_basic_agent.py +++ b/examples/agents/01_basic_agent.py @@ -1,15 +1,12 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Basic Agent — 5-line hello world. Demonstrates the simplest possible agent: define an agent, call ``runtime.run()``, and print the result. Requirements: - - Agentspan server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL set in .env or environment (optional) + - Conductor server with LLM support + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL set in .env or environment (optional) """ from conductor.ai.agents import Agent, AgentRuntime @@ -33,7 +30,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.01_basic_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/02_tools.py b/examples/agents/02_tools.py index fd7c997b0..94349084b 100644 --- a/examples/agents/02_tools.py +++ b/examples/agents/02_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tools — multiple tools, async, approval. Demonstrates: @@ -10,8 +7,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, EventType, tool diff --git a/examples/agents/02a_simple_tools.py b/examples/agents/02a_simple_tools.py index 0f07cf46c..d4ec5ae9e 100644 --- a/examples/agents/02a_simple_tools.py +++ b/examples/agents/02a_simple_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Simple Tool Calling — two tools, the LLM picks the right one. The agent has two tools: one for weather, one for stock prices. @@ -11,8 +8,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -49,7 +46,7 @@ def get_stock_price(symbol: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.02a_simple_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/02b_multi_step_tools.py b/examples/agents/02b_multi_step_tools.py index dff52ae97..019970d9d 100644 --- a/examples/agents/02b_multi_step_tools.py +++ b/examples/agents/02b_multi_step_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Multi-Step Tool Calling — chained lookups and calculations. The agent has four tools. The prompt requires it to: @@ -18,8 +15,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from typing import List @@ -90,7 +87,7 @@ def send_summary_email(to: str, subject: str, body: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.02b_multi_step_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/02c_tool_retry_config.py b/examples/agents/02c_tool_retry_config.py index 9fdf23fed..66b38c693 100644 --- a/examples/agents/02c_tool_retry_config.py +++ b/examples/agents/02c_tool_retry_config.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tool retry configuration — customizing retry behavior per tool. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool diff --git a/examples/agents/03_structured_output.py b/examples/agents/03_structured_output.py index a4fe666ac..0f6b1e413 100644 --- a/examples/agents/03_structured_output.py +++ b/examples/agents/03_structured_output.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Structured Output — Pydantic output types. Demonstrates how to get typed, validated responses from an agent @@ -9,8 +6,8 @@ Requirements: - Conductor server with LLM support - pydantic installed - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from pydantic import BaseModel @@ -50,7 +47,7 @@ def get_weather(city: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.03_structured_output + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/04_http_and_mcp_tools.py b/examples/agents/04_http_and_mcp_tools.py index 3c569ed41..1783b8df6 100644 --- a/examples/agents/04_http_and_mcp_tools.py +++ b/examples/agents/04_http_and_mcp_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """HTTP and MCP Tools — server-side tools (no workers needed). Demonstrates: @@ -19,15 +16,15 @@ # Or start with auth (requires storing the secret as a credential): mcp-testkit --transport http --auth - # Store credentials via CLI or Agentspan UI: - agentspan credentials set HTTP_TEST_API_KEY - agentspan credentials set MCP_TEST_API_KEY + # Store credentials via CLI or Conductor UI: + the Conductor server credential store + the Conductor server credential store Requirements: - Conductor server with LLM support - mcp-testkit running on http://localhost:3001 (see setup above) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool, http_tool, mcp_tool @@ -92,7 +89,7 @@ def format_report(title: str, body: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.04_http_and_mcp_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/04_mcp_weather.py b/examples/agents/04_mcp_weather.py index 2e9417cbc..53696c560 100644 --- a/examples/agents/04_mcp_weather.py +++ b/examples/agents/04_mcp_weather.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """MCP Weather — using Conductor's MCP system tasks for live weather. Demonstrates the `mcp_tool()` function which uses Conductor's built-in @@ -20,17 +17,17 @@ # Or start with auth (requires storing the secret as a credential): mcp-testkit --transport http --auth - # Store credentials via CLI or Agentspan UI: - agentspan credentials set MCP_TEST_API_KEY + # Store credentials via CLI or Conductor UI: + the Conductor server credential store Requirements: - Conductor server with LLM support - mcp-testkit running on http://localhost:3001 (see setup above) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable Docker gotcha: - When the AgentSpan server runs in Docker (e.g. `agentspan server start`), + When the Conductor server runs in Docker (e.g. `Conductor server start`), the *server* makes the MCP calls — not your local process. The server resolves `localhost` to its own container loopback, not your host machine. @@ -84,7 +81,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.04_mcp_weather + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/05_handoffs.py b/examples/agents/05_handoffs.py index 9a2712d9e..25c388d67 100644 --- a/examples/agents/05_handoffs.py +++ b/examples/agents/05_handoffs.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Handoffs — agent delegating to sub-agents. Demonstrates the handoff strategy where the parent agent's LLM decides @@ -8,8 +5,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy, tool @@ -79,7 +76,7 @@ def get_pricing(product: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(support) # CLI alternative: - # agentspan deploy --package examples.05_handoffs + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(support) diff --git a/examples/agents/06_sequential_pipeline.py b/examples/agents/06_sequential_pipeline.py index 90bd004cc..75a7a805f 100644 --- a/examples/agents/06_sequential_pipeline.py +++ b/examples/agents/06_sequential_pipeline.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Sequential Pipeline — Agent >> Agent >> Agent. Demonstrates the sequential strategy where agents run in order and the @@ -10,8 +7,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -60,7 +57,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.06_sequential_pipeline + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/07_parallel_agents.py b/examples/agents/07_parallel_agents.py index 98366c512..40efdb17c 100644 --- a/examples/agents/07_parallel_agents.py +++ b/examples/agents/07_parallel_agents.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Parallel Agents — fan-out / fan-in. Demonstrates the parallel strategy where all sub-agents run concurrently @@ -8,8 +5,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -63,7 +60,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(analysis) # CLI alternative: - # agentspan deploy --package examples.07_parallel_agents + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(analysis) diff --git a/examples/agents/08_router_agent.py b/examples/agents/08_router_agent.py index 0eb06c8a7..11a729fe8 100644 --- a/examples/agents/08_router_agent.py +++ b/examples/agents/08_router_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Router Agent — LLM-based routing to specialists. Demonstrates the router strategy where a dedicated router/classifier agent @@ -17,8 +14,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -77,7 +74,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(team) # CLI alternative: - # agentspan deploy --package examples.08_router_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(team) diff --git a/examples/agents/09_human_in_the_loop.py b/examples/agents/09_human_in_the_loop.py index efefc6875..c88d9de85 100644 --- a/examples/agents/09_human_in_the_loop.py +++ b/examples/agents/09_human_in_the_loop.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Human-in-the-Loop — approval workflows. Demonstrates how tools with approval_required=True pause the workflow @@ -9,8 +6,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, EventType, tool diff --git a/examples/agents/09b_hitl_with_feedback.py b/examples/agents/09b_hitl_with_feedback.py index 02b083624..1d846ad86 100644 --- a/examples/agents/09b_hitl_with_feedback.py +++ b/examples/agents/09b_hitl_with_feedback.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Human-in-the-Loop with Custom Feedback. Demonstrates the general-purpose `respond()` API. Instead of a binary @@ -14,8 +11,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, EventType, tool diff --git a/examples/agents/09c_hitl_streaming.py b/examples/agents/09c_hitl_streaming.py index a3d41eef2..b5b77be77 100644 --- a/examples/agents/09c_hitl_streaming.py +++ b/examples/agents/09c_hitl_streaming.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Human-in-the-Loop with Streaming — Console Interactive. Streams agent events in real time via SSE. When the agent pauses for @@ -13,8 +10,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, EventType, tool diff --git a/examples/agents/09d_human_tool.py b/examples/agents/09d_human_tool.py index d672f4377..b80187923 100644 --- a/examples/agents/09d_human_tool.py +++ b/examples/agents/09d_human_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Human Tool — LLM-initiated human interaction. Unlike approval_required tools (09_human_in_the_loop.py) where humans gate @@ -19,8 +16,8 @@ - The LLM using human input to make decisions Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL (default: openai/gpt-4o-mini) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL (default: openai/gpt-4o-mini) """ from settings import settings diff --git a/examples/agents/100_issue_fixer_agent.py b/examples/agents/100_issue_fixer_agent.py index 467724f40..a77615d57 100644 --- a/examples/agents/100_issue_fixer_agent.py +++ b/examples/agents/100_issue_fixer_agent.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Issue Fixer Agent — autonomous GitHub issue to PR pipeline. A multi-agent coding agent that takes a GitHub issue number, analyzes the @@ -23,8 +20,8 @@ python 100_issue_fixer_agent.py 42 Requirements: - - Agentspan server running - - GH_TOKEN: agentspan credentials set GH_TOKEN + - Conductor server running + - GH_TOKEN: the Conductor server credential store - gh CLI installed and authenticated - DG skill: git clone https://github.com/v1r3n/dinesh-gilfoyle ~/.claude/skills/dg - Full build toolchain (Go, Java 21, Python 3.10+, Node.js, pnpm, uv) @@ -52,7 +49,7 @@ ) # ── Project-Specific Configuration ──────────────────────────── -REPO = "agentspan-ai/agentspan" +REPO = "Conductor-ai/Conductor" REPO_URL = f"https://github.com/{REPO}" BRANCH_PREFIX = "fix/issue-" @@ -461,7 +458,7 @@ def main(): pr_number = args.pr # Create a temp working directory with a random suffix. - work_dir = os.path.join(tempfile.gettempdir(), f"agentspan-fix-{uuid.uuid4().hex[:12]}") + work_dir = os.path.join(tempfile.gettempdir(), f"Conductor-fix-{uuid.uuid4().hex[:12]}") set_working_dir(work_dir) print(f"Working directory: {work_dir}") diff --git a/examples/agents/103_plan_and_compile.py b/examples/agents/103_plan_and_compile.py index 883b29f5e..4e721fe6b 100644 --- a/examples/agents/103_plan_and_compile.py +++ b/examples/agents/103_plan_and_compile.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """PLAN_AND_COMPILE — server-side plan compiler in action. A planner agent produces a JSON DAG; the server's ``PLAN_AND_COMPILE`` Java @@ -17,13 +14,13 @@ - a ``validation`` block with a sandboxed success_condition Usage: - AGENTSPAN_SERVER_URL=http://localhost:8080/api \\ + CONDUCTOR_SERVER_URL=http://localhost:8080/api \\ OPENAI_API_KEY=... \\ python 103_plan_and_compile.py "Compute factorials of 1..5 and explain" Requirements: - - Agentspan server running with PLAN_AND_COMPILE registered - - OPENAI_API_KEY (or whichever provider matches AGENTSPAN_LLM_MODEL) + - Conductor server running with PLAN_AND_COMPILE registered + - OPENAI_API_KEY (or whichever provider matches CONDUCTOR_AGENT_LLM_MODEL) """ import math @@ -36,7 +33,7 @@ from settings import settings -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") CONDUCTOR_BASE = SERVER_URL.rstrip("/").replace("/api", "") diff --git a/examples/agents/104_plan_execute_guardrails.py b/examples/agents/104_plan_execute_guardrails.py index b08df8734..21f0fc76e 100644 --- a/examples/agents/104_plan_execute_guardrails.py +++ b/examples/agents/104_plan_execute_guardrails.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """PLAN_EXECUTE with tool guardrails. A PLAN_EXECUTE harness over the same tools as ``02_tools.py`` (weather, @@ -20,13 +17,13 @@ deterministic plan, and the harness's ``fallback`` agent recovers. Run: - AGENTSPAN_SERVER_URL=http://localhost:8080/api \\ + CONDUCTOR_SERVER_URL=http://localhost:8080/api \\ OPENAI_API_KEY=... \\ python 104_plan_execute_guardrails.py [topic] Requirements: - - Agentspan server running with PLAN_AND_COMPILE - - OPENAI_API_KEY (or matching provider for AGENTSPAN_LLM_MODEL) + - Conductor server running with PLAN_AND_COMPILE + - OPENAI_API_KEY (or matching provider for CONDUCTOR_AGENT_LLM_MODEL) """ import os @@ -46,7 +43,7 @@ from settings import settings -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") CONDUCTOR_BASE = SERVER_URL.rstrip("/").replace("/api", "") diff --git a/examples/agents/106_plan_execute_agent_fanout.py b/examples/agents/106_plan_execute_agent_fanout.py index cba04be63..d26dd7477 100644 --- a/examples/agents/106_plan_execute_agent_fanout.py +++ b/examples/agents/106_plan_execute_agent_fanout.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """PLAN_EXECUTE with agent fan-out — Conductor-native, fully declarative. Demonstrates the fix that makes ``Strategy.PLAN_EXECUTE`` route plan ops by @@ -40,7 +37,7 @@ python 106_plan_execute_agent_fanout.py Requires: - - Agentspan server running (AGENTSPAN_SERVER_URL) + - Conductor server running (CONDUCTOR_SERVER_URL) - OPENAI_API_KEY (planner LLM gets called even when ``plan=`` is injected — its output is discarded but the call has to land somewhere) """ diff --git a/examples/agents/107_pac_mcp_proof.py b/examples/agents/107_pac_mcp_proof.py index 495ce47a2..284c83b34 100644 --- a/examples/agents/107_pac_mcp_proof.py +++ b/examples/agents/107_pac_mcp_proof.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """PAC end-to-end proof: PLAN_EXECUTE routes by toolType. Sends a single typed Plan that mixes THREE tool types so the compiled @@ -25,8 +22,8 @@ # 1. Start mcp-testkit: uv run mcp-testkit --transport http --port 3001 - # 2. (Re)start agentspan server with the new PAC build: - kill + # 2. (Re)start Conductor server with the new PAC build: + kill cd server && ./gradlew bootRun # 3. Run this script: @@ -48,11 +45,11 @@ # ── Endpoints ───────────────────────────────────────────────────────── -AGENTSPAN_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") -# Conductor REST runs alongside agentspan; we'll read the compiled +CONDUCTOR_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") +# The agent runtime uses Conductor REST; read the compiled # WorkflowDef directly off Conductor to *prove* PAC emitted the right # task types (not just trust the SDK's view of execution.status). -CONDUCTOR_BASE = AGENTSPAN_URL.replace("/api", "") +CONDUCTOR_BASE = CONDUCTOR_URL.replace("/api", "") MCP_URL = "http://localhost:3001/mcp" @@ -215,7 +212,7 @@ def find_compiled_workflow_def(parent_id: str) -> tuple[str, dict]: if wd: # Read the WorkflowDef out of PAC's task output directly. # The /metadata/workflow/{name} endpoint returns only the - # placeholder agentspan registered up-front; PAC compiles + # placeholder Conductor registered up-front; PAC compiles # a fresh def per execution and emits it here. return out.get("workflowName", ""), wd sub = t.get("subWorkflowId") @@ -248,7 +245,7 @@ def main() -> int: print("=" * 70) print(" PAC end-to-end proof — PLAN_EXECUTE with toolType routing") print("=" * 70) - print(f" agentspan: {AGENTSPAN_URL}") + print(f" Conductor: {CONDUCTOR_URL}") print(f" conductor: {CONDUCTOR_BASE}") print(f" mcp: {MCP_URL}") print() diff --git a/examples/agents/108_plan_execute_refs.py b/examples/agents/108_plan_execute_refs.py index 717054e0d..80dcc4f4d 100644 --- a/examples/agents/108_plan_execute_refs.py +++ b/examples/agents/108_plan_execute_refs.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. - """108 — Plan-Execute with cross-step output piping via ``Ref``. The ``Ref("step_id")`` helper wires the **whole output** of an upstream @@ -31,8 +28,8 @@ independently (two Refs in the same args map). Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (default) - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini (default) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (default) + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini (default) """ from __future__ import annotations @@ -81,7 +78,7 @@ def main() -> None: name="ref_demo", tools=[produce, enrich, report], planner_instructions="(planner unused; static plan supplied)", - model=os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6"), + model=os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6"), ) plan = Plan( @@ -124,7 +121,7 @@ def _show_pipeline_outputs(execution_id: str) -> None: import requests - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") parent = requests.get( diff --git a/examples/agents/109_plan_execute_replan.py b/examples/agents/109_plan_execute_replan.py index 545d7c0cb..9b4f7de7d 100644 --- a/examples/agents/109_plan_execute_replan.py +++ b/examples/agents/109_plan_execute_replan.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """109 — Plan-Execute-Replan loop on top of PAE. The ``Strategy.PLAN_EXECUTE`` harness gives you a deterministic compiled @@ -40,8 +37,8 @@ * The loop exits when the threshold is met OR ``max_iterations`` hits. Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (default) - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini (default) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (default) + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini (default) - An LLM key for the chosen model (sections are generated, not static). """ @@ -346,7 +343,7 @@ def main(argv: list[str]) -> None: name="report_replan", tools=[create_directory, write_file, assemble_files, check_word_count], planner_instructions="(planner unused; plans supplied directly each iteration)", - model=os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6"), + model=os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6"), ) with AgentRuntime() as runtime: diff --git a/examples/agents/10_guardrails.py b/examples/agents/10_guardrails.py index 548971ebf..3283362c0 100644 --- a/examples/agents/10_guardrails.py +++ b/examples/agents/10_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Guardrails — output validation with tool calls. Demonstrates a guardrail that catches PII leaking from tool results into @@ -22,8 +19,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import re @@ -129,7 +126,7 @@ def no_pii(content: str) -> GuardrailResult: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.10_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/110_plan_execute_replan_solve.py b/examples/agents/110_plan_execute_replan_solve.py index 53a3c9e59..6b6b26725 100644 --- a/examples/agents/110_plan_execute_replan_solve.py +++ b/examples/agents/110_plan_execute_replan_solve.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """110 — Plan-Execute-Replan goal-seeking loop. Example 109 demonstrated the *shape* of an outer replan loop (one @@ -44,8 +41,8 @@ 3. It has exactly EXPECTED_WORD_COUNT words (default 20). Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (default) - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini (default) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (default) + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini (default) - LLM key for the chosen model. """ @@ -356,7 +353,7 @@ def main(argv: list[str]) -> None: name="sentence_solver", tools=[write_candidate, verify_candidates], planner_instructions="(planner unused; plans supplied directly each iteration)", - model=os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6"), + model=os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6"), ) with AgentRuntime() as runtime: diff --git a/examples/agents/111_plan_execute_replan_binsearch.py b/examples/agents/111_plan_execute_replan_binsearch.py index 765744a90..d2c211766 100644 --- a/examples/agents/111_plan_execute_replan_binsearch.py +++ b/examples/agents/111_plan_execute_replan_binsearch.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """111 — Plan-Execute-Replan with GUARANTEED many-iteration convergence. The other replan examples (109, 110) can converge in 1-2 iterations @@ -35,10 +32,10 @@ see a loop running. Many times. As intended. Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (default) - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini (default) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (default) + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini (default) - LLM key for the chosen model. - - AGENTSPAN_BINSEARCH_SECRET (optional override; default 642) + - CONDUCTOR_AGENT_BINSEARCH_SECRET (optional override; default 642) """ import json @@ -60,7 +57,7 @@ def _pick_secret() -> int: """Default secret is deliberately off the obvious binary-search midpoints so the LLM can't hit it on iter 0 by guessing 500. Override via env var to test convergence on other targets.""" - env = os.environ.get("AGENTSPAN_BINSEARCH_SECRET") + env = os.environ.get("CONDUCTOR_AGENT_BINSEARCH_SECRET") if env: return int(env) return 642 @@ -270,7 +267,7 @@ def main(argv: list[str]) -> None: name="binsearch", tools=[write_guess, check_guess], planner_instructions="(planner unused; plans supplied directly each iteration)", - model=os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6"), + model=os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6"), ) with AgentRuntime() as runtime: diff --git a/examples/agents/112_dowhile_loop_inside_workflow.py b/examples/agents/112_dowhile_loop_inside_workflow.py index 1a2461e42..f90c1e1b0 100644 --- a/examples/agents/112_dowhile_loop_inside_workflow.py +++ b/examples/agents/112_dowhile_loop_inside_workflow.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """112 — Plan-Execute-Replan loop INSIDE a single Conductor workflow. Examples 109/110/111 keep the replan loop in Python user code: each @@ -39,15 +36,15 @@ is under the budget. This is the shape of a *first-class* ``Strategy.PLAN_EXECUTE_REPLAN`` -that doesn't exist in Agentspan today (dg-review finding F1, +that doesn't exist in Conductor today (dg-review finding F1, recommendation #2). The example builds it by hand to show the full plan→compile→execute→replan structure end-to-end inside one workflow. Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (default) - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini (default) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (default) + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini (default) - LLM key for the chosen model. - - AGENTSPAN_BINSEARCH_SECRET (optional override; default 642) + - CONDUCTOR_AGENT_BINSEARCH_SECRET (optional override; default 642) """ import json @@ -60,11 +57,11 @@ from conductor.ai.agents import AgentRuntime, plan_execute, tool -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") BASE = SERVER_URL.rstrip("/").replace("/api", "") -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6") -SECRET = int(os.environ.get("AGENTSPAN_BINSEARCH_SECRET", "642")) -MAX_ITER = int(os.environ.get("AGENTSPAN_DOWHILE_MAX_ITER", "12")) +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6") +SECRET = int(os.environ.get("CONDUCTOR_AGENT_BINSEARCH_SECRET", "642")) +MAX_ITER = int(os.environ.get("CONDUCTOR_AGENT_DOWHILE_MAX_ITER", "12")) WORKFLOW_NAME = "pae_replan_dowhile_demo" WORKFLOW_VERSION = 5 diff --git a/examples/agents/113_aml_sar_investigation_loop.py b/examples/agents/113_aml_sar_investigation_loop.py index 5697a5ef3..d65821392 100644 --- a/examples/agents/113_aml_sar_investigation_loop.py +++ b/examples/agents/113_aml_sar_investigation_loop.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """113 — AML / SAR investigation loop with real PAC + SUB_WORKFLOW per turn. A BSA/AML alert fires on a customer (structuring pattern). An @@ -28,8 +25,8 @@ ``finalize_disposition``. Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (default) - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini (default) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (default) + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini (default) - LLM key for the chosen model. """ @@ -43,10 +40,10 @@ from conductor.ai.agents import AgentRuntime, plan_execute, tool -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") BASE = SERVER_URL.rstrip("/").replace("/api", "") -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6") -MAX_ITER = int(os.environ.get("AGENTSPAN_AML_MAX_ITER", "10")) +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6") +MAX_ITER = int(os.environ.get("CONDUCTOR_AGENT_AML_MAX_ITER", "10")) WORKFLOW_NAME = "aml_sar_investigation_loop" WORKFLOW_VERSION = 5 @@ -316,7 +313,7 @@ def finalize_disposition( # Pull the JSON action out of the LLM's response. Two shapes possible: -# (a) Agentspan's LLM_CHAT_COMPLETE auto-parses a JSON-mode response, so +# (a) Conductor's LLM_CHAT_COMPLETE auto-parses a JSON-mode response, so # ``$.llm_out`` is already a Java Map. Walk it to a JS object. # (b) Plaintext path: ``$.llm_out`` is a string; regex out the JSON block. EXTRACT_ACTION_JS = TO_JS_OBJ_JS + ( @@ -727,7 +724,7 @@ def main(argv: list[str]) -> None: print(f" customer={ALERT['customer_id']}, ${ALERT['total_amount']:,} over 5 days") print(f"budget: {MAX_ITER} iterations\n") - # Register the workers via Agentspan runtime. + # Register the workers via Conductor runtime. print("setting up evidence-source workers via AgentRuntime...") harness = plan_execute( name="aml_tools_harness", diff --git a/examples/agents/114_portfolio_rebalance_loop.py b/examples/agents/114_portfolio_rebalance_loop.py index 65293283d..363c82d7b 100644 --- a/examples/agents/114_portfolio_rebalance_loop.py +++ b/examples/agents/114_portfolio_rebalance_loop.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """114 — Wealth-management portfolio rebalancing loop with real PAC + SUB_WORKFLOW. An RIA portfolio is off target. Each iteration the planner LLM proposes a @@ -30,8 +27,8 @@ * Termination on the iteration where submit_trades is called. Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (default) - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini (default) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (default) + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini (default) - LLM key for the chosen model. """ @@ -45,10 +42,10 @@ from conductor.ai.agents import AgentRuntime, plan_execute, tool -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") BASE = SERVER_URL.rstrip("/").replace("/api", "") -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6") -MAX_ITER = int(os.environ.get("AGENTSPAN_REBAL_MAX_ITER", "8")) +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6") +MAX_ITER = int(os.environ.get("CONDUCTOR_AGENT_REBAL_MAX_ITER", "8")) WORKFLOW_NAME = "portfolio_rebalance_loop" WORKFLOW_VERSION = 6 diff --git a/examples/agents/115_plan_execute_planner_context.py b/examples/agents/115_plan_execute_planner_context.py index 11b7e2ff5..0f78afdcf 100644 --- a/examples/agents/115_plan_execute_planner_context.py +++ b/examples/agents/115_plan_execute_planner_context.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. - """115 — Plan-Execute with ``planner_context``: customer onboarding plan. The PAE planner's static ``instructions`` string is fine for *how* to @@ -57,8 +54,8 @@ the markdown that gets templated into the planner's user message. Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (default) - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini (default) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (default) + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini (default) """ from __future__ import annotations @@ -114,7 +111,7 @@ def schedule_kickoff_call(customer_id: str, account_id: str) -> dict: def main() -> None: - model = os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6") + model = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6") planner = Agent( name="onboarding_planner", @@ -202,7 +199,7 @@ def _show_executed_steps(execution_id: str) -> None: """Walk into the plan_exec sub-workflow and print the tool tasks.""" import requests - base = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + base = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") base_url = base.rstrip("/").replace("/api", "") parent = requests.get( diff --git a/examples/agents/116_ocg_subagent.py b/examples/agents/116_ocg_subagent.py index bd5cbb8c6..a90c8712f 100644 --- a/examples/agents/116_ocg_subagent.py +++ b/examples/agents/116_ocg_subagent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. - """116 — OCG retrieval via the prebuilt sub-agent. The main agent delegates retrieval to an OCG (Open Context Graph) @@ -33,7 +30,7 @@ uv run python examples/116_ocg_subagent.py # against an embedded server (e.g. orkes on 8080), add: - # AGENTSPAN_SERVER_URL=http://localhost:8080/api + # CONDUCTOR_SERVER_URL=http://localhost:8080/api """ import os @@ -41,7 +38,7 @@ from conductor.ai.agents import Agent, AgentRuntime, agent_tool from conductor.ai.agents.ocg import ocg_agent -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6") # Per-tool instance binding — required: every OCG tool binds the instance # it talks to; there is no server-side default. diff --git a/examples/agents/117_ocg_direct_tools.py b/examples/agents/117_ocg_direct_tools.py index dfb3a6c08..bf2527f80 100644 --- a/examples/agents/117_ocg_direct_tools.py +++ b/examples/agents/117_ocg_direct_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. - """117 — OCG retrieval via a direct tool call (no sub-agent). The main agent holds the OCG query tool *itself*: ``ocg_tools()`` @@ -33,7 +30,7 @@ uv run python examples/117_ocg_direct_tools.py # against an embedded server (e.g. orkes on 8080), add: - # AGENTSPAN_SERVER_URL=http://localhost:8080/api + # CONDUCTOR_SERVER_URL=http://localhost:8080/api """ import os @@ -41,7 +38,7 @@ from conductor.ai.agents import Agent, AgentRuntime from conductor.ai.agents.ocg import ocg_tools -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6") OCG_INSTANCE_URL = os.environ.get("OCG_INSTANCE_URL") or "" OCG_CREDENTIAL = os.environ.get("OCG_CREDENTIAL") # credential-store name, never the key diff --git a/examples/agents/118_adaptive_loop_showcase.py b/examples/agents/118_adaptive_loop_showcase.py index 50d577ba4..acb0f3579 100644 --- a/examples/agents/118_adaptive_loop_showcase.py +++ b/examples/agents/118_adaptive_loop_showcase.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """118 — Adaptive loop: travel planner that iterates inside a single agent execution. ONE runtime.run() call. ONE execution ID. The agent loops inside that @@ -19,7 +16,7 @@ Every tool call (each attempt + verdict) is logged under one execution ID and visible in the UI at http://localhost:8080. -This is the correct Agentspan adaptive loop pattern — not Python +This is the correct Conductor adaptive loop pattern — not Python coordinating multiple runtime.run() calls, but the agent itself iterating within a single durable server-side execution. @@ -32,7 +29,7 @@ 6. Evening must be the most expensive slot each day. Usage: - agentspan server start + Conductor server start export OPENAI_API_KEY=sk-... uv run python3 118_adaptive_loop_showcase.py "Tokyo" uv run python3 118_adaptive_loop_showcase.py "Paris" --budget 60 @@ -55,7 +52,7 @@ MIN_PAID_COST: int = 15 # at least one activity per day must cost ≥ this MIN_DAILY_SPEND: int = 20 # each day must spend at least this much NUM_DAYS: int = 3 -MODEL: str = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +MODEL: str = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") # ── Validation tool (deterministic — no LLM) ───────────────────────────────── diff --git a/examples/agents/119_research_report_pae_replan.py b/examples/agents/119_research_report_pae_replan.py index 55fafc44a..150193b88 100644 --- a/examples/agents/119_research_report_pae_replan.py +++ b/examples/agents/119_research_report_pae_replan.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """119 — Research report: Plan-Execute-Review-Replan loop as one Conductor execution. A research report is generated iteratively inside a single Conductor workflow. @@ -33,7 +30,7 @@ → Quality improvements iteration by iteration. Requirements: - - agentspan server start + - Conductor server start - export OPENAI_API_KEY=sk-... (or ANTHROPIC_API_KEY) - uv run python3 119_research_report_pae_replan.py "AI agents in production" """ @@ -52,9 +49,9 @@ from conductor.ai.agents import AgentRuntime, plan_execute, tool -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") BASE = SERVER_URL.rstrip("/").replace("/api", "") -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") MAX_ITER = int(os.environ.get("REPORT_MAX_ITER", "5")) WORKFLOW_NAME = "research_report_pae_replan" WORKFLOW_VERSION = 3 diff --git a/examples/agents/11_streaming.py b/examples/agents/11_streaming.py index 6429366fa..ca14633e3 100644 --- a/examples/agents/11_streaming.py +++ b/examples/agents/11_streaming.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Streaming — real-time events. Demonstrates streaming agent execution events. The runtime.stream() method @@ -8,8 +5,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime @@ -33,7 +30,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.11_streaming + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/12_long_running.py b/examples/agents/12_long_running.py index b87c04587..13b79c9cb 100644 --- a/examples/agents/12_long_running.py +++ b/examples/agents/12_long_running.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Long-Running Agent — fire-and-forget with status checking. Demonstrates starting an agent asynchronously and checking its status @@ -9,8 +6,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import time @@ -37,7 +34,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.12_long_running + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/13_hierarchical_agents.py b/examples/agents/13_hierarchical_agents.py index ef10fcbc1..3ee8aa795 100644 --- a/examples/agents/13_hierarchical_agents.py +++ b/examples/agents/13_hierarchical_agents.py @@ -17,8 +17,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy, OnTextMention @@ -110,16 +110,15 @@ if __name__ == "__main__": with AgentRuntime() as runtime: print("--- Technical question (CEO -> Engineering -> Backend) ---") - result = runtime.run(ceo, "Design a REST API for a user management system with authentication " - "and then ask marketing team to come up with a marketing campaign for the system with details on how to run these campaign") - result.print_result() + # result = runtime.run(ceo, "Design a REST API for a user management system with authentication " + # "and then ask marketing team to come up with a marketing campaign for the system with details on how to run these campaign") + # result.print_result() # Production pattern: # 1. Deploy once during CI/CD: # runtime.deploy(ceo) # CLI alternative: - # agentspan deploy --package examples.13_hierarchical_agents + # runtime.deploy(ceo) # # 2. In a separate long-lived worker process: - # runtime.serve(ceo) - + runtime.serve(ceo) diff --git a/examples/agents/14_existing_workers.py b/examples/agents/14_existing_workers.py index 1092476cb..f59966cc2 100644 --- a/examples/agents/14_existing_workers.py +++ b/examples/agents/14_existing_workers.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Existing Workers — use @worker_task functions as agent tools. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - Conductor server with LLM support - conductor-python installed (provides @worker_task) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.client.worker.worker_task import worker_task @@ -79,7 +76,7 @@ def create_support_ticket(customer_id: str, issue: str, priority: str = "medium" # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.14_existing_workers + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/15_agent_discussion.py b/examples/agents/15_agent_discussion.py index 0fb8e723d..2d8ba5298 100644 --- a/examples/agents/15_agent_discussion.py +++ b/examples/agents/15_agent_discussion.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent Discussion — durable round-robin debate compiled to a Conductor DoWhile loop. Demonstrates a multi-turn discussion between agents with opposing @@ -20,8 +17,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -88,7 +85,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.15_agent_discussion + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/16_credentials_isolated_tool.py b/examples/agents/16_credentials_isolated_tool.py index 0b6721ec5..91840c555 100644 --- a/examples/agents/16_credentials_isolated_tool.py +++ b/examples/agents/16_credentials_isolated_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — per-user secrets injected into isolated tool subprocesses. Demonstrates: @@ -17,13 +14,13 @@ injected as env vars. The parent process's os.environ is unchanged. Setup (one-time, via CLI): - agentspan login # authenticate - agentspan credentials set GH_TOKEN # enter token when prompted + Conductor login # authenticate + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) - - GH_TOKEN stored via `agentspan credentials set` OR set in os.environ + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) + - GH_TOKEN stored via `the Conductor server credential store` OR set in os.environ """ import os @@ -129,7 +126,7 @@ def create_github_issue(repo: str, title: str, body: str) -> dict: with AgentRuntime() as runtime: result = runtime.run( agent, - "List the 5 most recently updated repos for the 'agentspan-ai' GitHub org.", + "List the 5 most recently updated repos for the 'Conductor-ai' GitHub org.", ) result.print_result() @@ -137,7 +134,7 @@ def create_github_issue(repo: str, title: str, body: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.16_credentials_isolated_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/16_random_strategy.py b/examples/agents/16_random_strategy.py index de0dbec49..848b9181c 100644 --- a/examples/agents/16_random_strategy.py +++ b/examples/agents/16_random_strategy.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Random Strategy — random agent selection each turn. Demonstrates the ``strategy="random"`` pattern where a random sub-agent @@ -9,8 +6,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -65,7 +62,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(brainstorm) # CLI alternative: - # agentspan deploy --package examples.16_random_strategy + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(brainstorm) diff --git a/examples/agents/16b_credentials_non_isolated.py b/examples/agents/16b_credentials_non_isolated.py index 3fe28f32f..e47a7e213 100644 --- a/examples/agents/16b_credentials_non_isolated.py +++ b/examples/agents/16b_credentials_non_isolated.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — accessing injected secrets in-process with get_secret(). Demonstrates: @@ -14,9 +11,9 @@ is read from process environment variables. Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults via settings) - - STRIPE_SECRET_KEY stored: agentspan credentials set STRIPE_SECRET_KEY + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults via settings) + - STRIPE_SECRET_KEY stored: the Conductor server credential store """ from settings import settings @@ -40,7 +37,7 @@ def get_customer_balance(customer_id: str) -> dict: api_key = get_secret("STRIPE_SECRET_KEY") except CredentialNotFoundError: return { - "error": "STRIPE_SECRET_KEY not configured — run: agentspan credentials set STRIPE_SECRET_KEY " + "error": "STRIPE_SECRET_KEY is not configured in the Conductor server credential store." } import base64 @@ -122,5 +119,5 @@ def list_recent_charges(limit: int = 5) -> dict: # Production pattern: # 1. Deploy once during CI/CD: # runtime.deploy(agent) - # CLI alternative: agentspan deploy --package examples.16b_credentials_non_isolated + # CLI alternative: runtime.deploy(agent) from a release script # 2. In a separate long-lived worker process: runtime.serve(agent) diff --git a/examples/agents/16c_credentials_cli_tools.py b/examples/agents/16c_credentials_cli_tools.py index a2ace7211..b50bb87f9 100644 --- a/examples/agents/16c_credentials_cli_tools.py +++ b/examples/agents/16c_credentials_cli_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — CLI tools with explicit credential declarations. Demonstrates: @@ -10,13 +7,13 @@ - Multi-credential tools (aws needs multiple env vars) Setup (one-time, via CLI): - agentspan login - agentspan credentials set GH_TOKEN - agentspan credentials set AWS_ACCESS_KEY_ID - agentspan credentials set AWS_SECRET_ACCESS_KEY + Conductor login + the Conductor server credential store + the Conductor server credential store + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) - gh and aws CLIs installed """ @@ -131,7 +128,7 @@ def aws_get_caller_identity() -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(github_aws_agent) # CLI alternative: - # agentspan deploy --package examples.16c_credentials_cli_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(github_aws_agent) diff --git a/examples/agents/16d_credentials_gh_cli.py b/examples/agents/16d_credentials_gh_cli.py index 63588c534..c087f1017 100644 --- a/examples/agents/16d_credentials_gh_cli.py +++ b/examples/agents/16d_credentials_gh_cli.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — GitHub CLI (gh) with automatic credential injection. Demonstrates: @@ -9,13 +6,13 @@ - The agent calls `gh` commands directly — no subprocess boilerplate needed Setup (one-time, via CLI): - agentspan login - agentspan credentials set GH_TOKEN + Conductor login + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) - `gh` CLI installed (https://cli.github.com) - - GH_TOKEN stored via `agentspan credentials set` + - GH_TOKEN stored via `the Conductor server credential store` """ from conductor.ai.agents import Agent, AgentRuntime @@ -39,7 +36,7 @@ with AgentRuntime() as runtime: result = runtime.run( agent, - "List the 5 most recently updated repos for the 'agentspan'", + "List the 5 most recently updated repos for the 'Conductor'", ) result.print_result() @@ -47,7 +44,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.16d_credentials_gh_cli + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/16e_credentials_http_tool.py b/examples/agents/16e_credentials_http_tool.py index 85fe628ff..50e39ec3c 100644 --- a/examples/agents/16e_credentials_http_tool.py +++ b/examples/agents/16e_credentials_http_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — HTTP tool with server-side credential resolution. Demonstrates: @@ -13,11 +10,11 @@ in the workflow definition. Setup (one-time): - agentspan credentials set GITHUB_TOKEN + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) - - GITHUB_TOKEN stored via `agentspan credentials set` + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) + - GITHUB_TOKEN stored via `the Conductor server credential store` """ from conductor.ai.agents import Agent, AgentRuntime @@ -30,7 +27,7 @@ list_repos = http_tool( name="list_github_repos", description="List public GitHub repositories for a user. Returns JSON array with name, url, and stars.", - url="https://api.github.com/users/agentspan/repos?per_page=5&sort=updated", + url="https://api.github.com/users/Conductor/repos?per_page=5&sort=updated", headers={ "Authorization": "Bearer ${GITHUB_TOKEN}", "Accept": "application/vnd.github.v3+json", @@ -48,14 +45,14 @@ if __name__ == "__main__": with AgentRuntime() as runtime: - result = runtime.run(agent, "List the repos for agentspan") + result = runtime.run(agent, "List the repos for Conductor") result.print_result() # Production pattern: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.16e_credentials_http_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/16f_credentials_mcp_tool.py b/examples/agents/16f_credentials_mcp_tool.py index e20a8a983..5fa23c624 100644 --- a/examples/agents/16f_credentials_mcp_tool.py +++ b/examples/agents/16f_credentials_mcp_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — MCP tool with server-side credential resolution. Demonstrates: @@ -14,14 +11,14 @@ # Start with auth (to demonstrate credential resolution): mcp-testkit --transport http --auth - # Store credentials via CLI or Agentspan UI: - agentspan credentials set MCP_API_KEY + # Store credentials via CLI or Conductor UI: + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-4o-mini) + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-4o-mini) - mcp-testkit running on http://localhost:3001 (see setup above) - - MCP_API_KEY stored via CLI or Agentspan UI + - MCP_API_KEY stored via CLI or Conductor UI """ from conductor.ai.agents import Agent, AgentRuntime @@ -56,7 +53,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.16f_credentials_mcp_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/16g_credentials_framework_passthrough.py b/examples/agents/16g_credentials_framework_passthrough.py index 75ae15cab..b35114b3e 100644 --- a/examples/agents/16g_credentials_framework_passthrough.py +++ b/examples/agents/16g_credentials_framework_passthrough.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — Framework passthrough with credential injection. Demonstrates: @@ -10,15 +7,15 @@ - Works the same for LangChain, OpenAI Agent SDK, and Google ADK This pattern is used when you run a foreign framework agent (LangGraph, -LangChain, OpenAI, ADK) through Agentspan and need tools inside the +LangChain, OpenAI, ADK) through Conductor and need tools inside the graph to access credentials from the credential store. Setup (one-time): - agentspan credentials set GITHUB_TOKEN + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) - - GITHUB_TOKEN stored via `agentspan credentials set` + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) + - GITHUB_TOKEN stored via `the Conductor server credential store` - langgraph installed: pip install langgraph langchain-openai """ @@ -74,7 +71,7 @@ def create_langgraph_agent(): # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.16g_credentials_framework_passthrough + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/16h_credentials_external_worker.py b/examples/agents/16h_credentials_external_worker.py index 92f79be40..3d544ec6e 100644 --- a/examples/agents/16h_credentials_external_worker.py +++ b/examples/agents/16h_credentials_external_worker.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — External worker credential resolution. Demonstrates: @@ -19,11 +16,11 @@ simulate both in one file for demonstration. Setup (one-time): - agentspan credentials set GITHUB_TOKEN + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) - - GITHUB_TOKEN stored via `agentspan credentials set` + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) + - GITHUB_TOKEN stored via `the Conductor server credential store` """ from conductor.ai.agents import Agent, AgentRuntime, tool, resolve_credentials diff --git a/examples/agents/16i_credentials_langchain.py b/examples/agents/16i_credentials_langchain.py index da92b982d..39a7acdab 100644 --- a/examples/agents/16i_credentials_langchain.py +++ b/examples/agents/16i_credentials_langchain.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — LangChain agent with credential injection. Demonstrates: @@ -9,11 +6,11 @@ and injected into os.environ before the agent runs Setup (one-time): - agentspan credentials set GITHUB_TOKEN + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) - - GITHUB_TOKEN stored via `agentspan credentials set` + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) + - GITHUB_TOKEN stored via `the Conductor server credential store` - langchain installed: pip install langchain langchain-openai """ @@ -70,7 +67,7 @@ def create_langchain_agent(): # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.16i_credentials_langchain + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/16j_credentials_openai_sdk.py b/examples/agents/16j_credentials_openai_sdk.py index e1b15e05a..b0cdf8393 100644 --- a/examples/agents/16j_credentials_openai_sdk.py +++ b/examples/agents/16j_credentials_openai_sdk.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — OpenAI Agent SDK with credential injection. Demonstrates: @@ -9,11 +6,11 @@ - OpenAI agent tools can read credentials from os.environ Setup (one-time): - agentspan credentials set GITHUB_TOKEN + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) - - GITHUB_TOKEN stored via `agentspan credentials set` + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) + - GITHUB_TOKEN stored via `the Conductor server credential store` - openai-agents installed: pip install openai-agents """ @@ -64,7 +61,7 @@ def create_openai_agent(): # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.16j_credentials_openai_sdk + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/16k_credentials_google_adk.py b/examples/agents/16k_credentials_google_adk.py index b0a2634a1..ef0415cc9 100644 --- a/examples/agents/16k_credentials_google_adk.py +++ b/examples/agents/16k_credentials_google_adk.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credentials — Google ADK agent with credential injection. Demonstrates: @@ -9,11 +6,11 @@ and injected into os.environ before agent execution Setup (one-time): - agentspan credentials set GITHUB_TOKEN + the Conductor server credential store Requirements: - - Agentspan server running at AGENTSPAN_SERVER_URL - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-5.4) - - GITHUB_TOKEN stored via `agentspan credentials set` + - Conductor server running at CONDUCTOR_SERVER_URL + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-5.4) + - GITHUB_TOKEN stored via `the Conductor server credential store` - google-adk installed: pip install google-adk """ @@ -58,7 +55,7 @@ def check_github_auth() -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.16k_credentials_google_adk + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/17_swarm_orchestration.py b/examples/agents/17_swarm_orchestration.py index 48a45c279..318b0debb 100644 --- a/examples/agents/17_swarm_orchestration.py +++ b/examples/agents/17_swarm_orchestration.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Swarm Orchestration — automatic agent transitions via transfer tools. Demonstrates ``strategy="swarm"`` with LLM-driven, tool-based handoffs. @@ -20,8 +17,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -85,7 +82,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(support) # CLI alternative: - # agentspan deploy --package examples.17_swarm_orchestration + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(support) diff --git a/examples/agents/18_manual_selection.py b/examples/agents/18_manual_selection.py index 7f4aed981..82adc65f4 100644 --- a/examples/agents/18_manual_selection.py +++ b/examples/agents/18_manual_selection.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Manual Selection — human picks which agent speaks next. Demonstrates ``strategy="manual"`` where the workflow pauses each turn @@ -15,8 +12,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, EventType, Strategy diff --git a/examples/agents/19_composable_termination.py b/examples/agents/19_composable_termination.py index 34ceab431..2a8207d26 100644 --- a/examples/agents/19_composable_termination.py +++ b/examples/agents/19_composable_termination.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Composable Termination Conditions — AND/OR rules for stopping agents. Demonstrates composable termination conditions using ``&`` (AND) and @@ -13,8 +10,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import ( @@ -103,7 +100,7 @@ def search(query: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent1) # CLI alternative: - # agentspan deploy --package examples.19_composable_termination + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent1) diff --git a/examples/agents/20_constrained_transitions.py b/examples/agents/20_constrained_transitions.py index e9e367a9b..69510ab70 100644 --- a/examples/agents/20_constrained_transitions.py +++ b/examples/agents/20_constrained_transitions.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Constrained Speaker Transitions — control which agents can follow which. Demonstrates ``allowed_transitions`` which restricts which agent can @@ -13,8 +10,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -77,7 +74,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(code_review) # CLI alternative: - # agentspan deploy --package examples.20_constrained_transitions + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(code_review) diff --git a/examples/agents/21_regex_guardrails.py b/examples/agents/21_regex_guardrails.py index dd498edaf..21eada7f8 100644 --- a/examples/agents/21_regex_guardrails.py +++ b/examples/agents/21_regex_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Regex Guardrails — pattern-based content validation. Demonstrates ``RegexGuardrail`` for blocking or allowing content based @@ -16,8 +13,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, OnFail, Position, RegexGuardrail, tool @@ -116,7 +113,7 @@ def get_user_profile(user_id: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.21_regex_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/22_llm_guardrails.py b/examples/agents/22_llm_guardrails.py index ab6ec1cd5..28b6d1fca 100644 --- a/examples/agents/22_llm_guardrails.py +++ b/examples/agents/22_llm_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """LLM Guardrails — AI-powered content safety evaluation. Demonstrates ``LLMGuardrail`` which uses a separate (typically smaller/faster) @@ -15,8 +12,8 @@ Requirements: - Conductor server with LLM support - pip install litellm (for the guardrail LLM call) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable - OPENAI_API_KEY=sk-... as environment variable """ @@ -67,7 +64,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.22_llm_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/23_token_tracking.py b/examples/agents/23_token_tracking.py index 63831d664..22c442979 100644 --- a/examples/agents/23_token_tracking.py +++ b/examples/agents/23_token_tracking.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Token & Cost Tracking — monitor LLM token usage per agent run. Demonstrates the ``TokenUsage`` field on ``AgentResult`` which provides @@ -8,8 +5,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -61,7 +58,7 @@ def calculate(expression: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.23_token_tracking + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/24_code_execution.py b/examples/agents/24_code_execution.py index c181faef5..a6705e07b 100644 --- a/examples/agents/24_code_execution.py +++ b/examples/agents/24_code_execution.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Code Execution — sandboxed environments for running LLM-generated code. Demonstrates all four code executor types: @@ -16,8 +13,8 @@ - Conductor server with LLM support - Docker (for DockerCodeExecutor example) - pip install jupyter_client ipykernel (for JupyterCodeExecutor) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime @@ -90,7 +87,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(coder) # CLI alternative: - # agentspan deploy --package examples.24_code_execution + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coder) diff --git a/examples/agents/25_semantic_memory.py b/examples/agents/25_semantic_memory.py index 569951a47..4a4879f18 100644 --- a/examples/agents/25_semantic_memory.py +++ b/examples/agents/25_semantic_memory.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Semantic Memory — long-term memory with similarity-based retrieval. Demonstrates ``SemanticMemory`` for persisting facts across sessions @@ -11,8 +8,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -80,7 +77,7 @@ def get_customer_context(query: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.25_semantic_memory + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/26_opentelemetry_tracing.py b/examples/agents/26_opentelemetry_tracing.py index 95f1c107b..442bd630c 100644 --- a/examples/agents/26_opentelemetry_tracing.py +++ b/examples/agents/26_opentelemetry_tracing.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenTelemetry Tracing — industry-standard observability. Demonstrates OTel instrumentation for agent execution. When @@ -16,8 +13,8 @@ Requirements: - pip install opentelemetry-api opentelemetry-sdk - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, is_tracing_enabled, tool @@ -75,7 +72,7 @@ def lookup(query: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.26_opentelemetry_tracing + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/28_gpt_assistant_agent.py b/examples/agents/28_gpt_assistant_agent.py index f55fa16eb..f1e2e74a8 100644 --- a/examples/agents/28_gpt_assistant_agent.py +++ b/examples/agents/28_gpt_assistant_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """GPTAssistantAgent — wrap OpenAI Assistants API as a Conductor agent. Demonstrates ``GPTAssistantAgent`` which uses the OpenAI Assistants API @@ -15,8 +12,8 @@ - pip install openai - Conductor server with LLM support - OPENAI_API_KEY=sk-... as environment variable - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import AgentRuntime @@ -57,7 +54,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(data_analyst) # CLI alternative: - # agentspan deploy --package examples.28_gpt_assistant_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(data_analyst) diff --git a/examples/agents/29_agent_introductions.py b/examples/agents/29_agent_introductions.py index e0920579b..5b46476f4 100644 --- a/examples/agents/29_agent_introductions.py +++ b/examples/agents/29_agent_introductions.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent Introductions — agents introduce themselves before a discussion. Demonstrates the ``introduction`` parameter on Agent, which adds a @@ -12,8 +9,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -88,7 +85,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(design_review) # CLI alternative: - # agentspan deploy --package examples.29_agent_introductions + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(design_review) diff --git a/examples/agents/30_multimodal_agent.py b/examples/agents/30_multimodal_agent.py index 6ff077dcb..e9c034949 100644 --- a/examples/agents/30_multimodal_agent.py +++ b/examples/agents/30_multimodal_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Multimodal Agent — analyze images and video with vision-capable models. Demonstrates multimodal input via the ``media`` parameter on @@ -15,8 +12,8 @@ Requirements: - Conductor server with LLM support (OpenAI key configured) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -136,7 +133,7 @@ def save_analysis(title: str, analysis: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(vision_agent) # CLI alternative: - # agentspan deploy --package examples.30_multimodal_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(vision_agent) diff --git a/examples/agents/30_skills_dg_review.py b/examples/agents/30_skills_dg_review.py index 74dbee04a..811d27890 100644 --- a/examples/agents/30_skills_dg_review.py +++ b/examples/agents/30_skills_dg_review.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Skills — Load /dg skill as a durable agent. Demonstrates: @@ -12,7 +9,7 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable - /dg skill installed (https://github.com/v1r3n/dinesh-gilfoyle) Install /dg: diff --git a/examples/agents/31_skills_conductor.py b/examples/agents/31_skills_conductor.py index 258bbc0a2..7385c3a46 100644 --- a/examples/agents/31_skills_conductor.py +++ b/examples/agents/31_skills_conductor.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Skills — Load conductor skill for workflow management. Demonstrates: @@ -11,7 +8,7 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable - conductor-skills installed (https://github.com/conductor-oss/conductor-skills) Install conductor-skills: diff --git a/examples/agents/31_tool_guardrails.py b/examples/agents/31_tool_guardrails.py index 44c4d2599..3caeedd70 100644 --- a/examples/agents/31_tool_guardrails.py +++ b/examples/agents/31_tool_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tool guardrails — pre-execution validation on tool inputs. Demonstrates a guardrail attached to a specific tool that blocks dangerous @@ -12,8 +9,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import re @@ -95,7 +92,7 @@ def run_query(query: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.31_tool_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/32_human_guardrail.py b/examples/agents/32_human_guardrail.py index 46c93aa60..8528634cd 100644 --- a/examples/agents/32_human_guardrail.py +++ b/examples/agents/32_human_guardrail.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Human-in-the-loop guardrail — ``on_fail="human"``. Demonstrates a guardrail that pauses the workflow for human review when @@ -9,8 +6,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import ( diff --git a/examples/agents/32_skills_multi_agent.py b/examples/agents/32_skills_multi_agent.py index 6ab32781d..b529f3ea4 100644 --- a/examples/agents/32_skills_multi_agent.py +++ b/examples/agents/32_skills_multi_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Skills — Multi-agent workflows with skills as sub-agents. Demonstrates: @@ -12,7 +9,7 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable - /dg skill installed (https://github.com/v1r3n/dinesh-gilfoyle) - conductor skill installed (https://github.com/conductor-oss/conductor-skills) """ diff --git a/examples/agents/33_external_workers.py b/examples/agents/33_external_workers.py index 5b910124f..2cc4e9894 100644 --- a/examples/agents/33_external_workers.py +++ b/examples/agents/33_external_workers.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """External Worker Tools — reference workers running in other services. Demonstrates ``@tool(external=True)`` for referencing Conductor workers that @@ -17,8 +14,8 @@ Requirements: - Conductor server with LLM support - The referenced workers must be running somewhere - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -101,7 +98,7 @@ def check_inventory(product_id: str, warehouse: str = "default") -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(support_agent) # CLI alternative: - # agentspan deploy --package examples.33_external_workers + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(support_agent) diff --git a/examples/agents/33_single_turn_tool.py b/examples/agents/33_single_turn_tool.py index 5ac84feae..1a38bf4f7 100644 --- a/examples/agents/33_single_turn_tool.py +++ b/examples/agents/33_single_turn_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Single-Turn Tool Call — LLM calls a tool and answers in one shot. The simplest tool-calling pattern: the user asks a question, the LLM @@ -13,8 +10,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -45,7 +42,7 @@ def get_weather(city: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.33_single_turn_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/35_standalone_guardrails.py b/examples/agents/35_standalone_guardrails.py index 4193883f0..2375220e8 100644 --- a/examples/agents/35_standalone_guardrails.py +++ b/examples/agents/35_standalone_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Standalone guardrails — use as plain callables or as Conductor workers. The ``@guardrail`` decorator produces a plain callable. You can: @@ -15,8 +12,8 @@ Requirements: Part 1 (standalone): none — no server, no LLM, no workers. Part 2 (as workers): Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import re diff --git a/examples/agents/36_simple_agent_guardrails.py b/examples/agents/36_simple_agent_guardrails.py index 03ecdace4..fa4945774 100644 --- a/examples/agents/36_simple_agent_guardrails.py +++ b/examples/agents/36_simple_agent_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Simple agent guardrails — output validation without tools. Demonstrates guardrails on a **simple agent** (no tools, no sub-agents). @@ -20,8 +17,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import ( @@ -114,7 +111,7 @@ def min_length(content: str) -> GuardrailResult: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.36_simple_agent_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/37_fix_guardrail.py b/examples/agents/37_fix_guardrail.py index 4b54aca91..69af47d53 100644 --- a/examples/agents/37_fix_guardrail.py +++ b/examples/agents/37_fix_guardrail.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Fix guardrail — auto-correct output instead of retrying. Demonstrates ``on_fail="fix"``: when the guardrail fails, it provides a @@ -19,8 +16,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import re @@ -141,7 +138,7 @@ def get_contact_info(name: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.37_fix_guardrail + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/38_tech_trends.py b/examples/agents/38_tech_trends.py index 75427ca01..d03833d02 100644 --- a/examples/agents/38_tech_trends.py +++ b/examples/agents/38_tech_trends.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """ Tech Trend Analyzer — Multi-agent research + analysis + PDF pipeline. @@ -28,9 +25,9 @@ Run: Export as environment variables: - AGENTSPAN_SERVER_URL=https://developer.orkescloud.com/api - AGENTSPAN_AUTH_KEY= - AGENTSPAN_AUTH_SECRET= + CONDUCTOR_SERVER_URL=https://developer.orkescloud.com/api + CONDUCTOR_AUTH_KEY= + CONDUCTOR_AUTH_SECRET= python 38_tech_trends.py """ @@ -323,7 +320,7 @@ def compare_numbers( # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.38_tech_trends + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/39_local_code_execution.py b/examples/agents/39_local_code_execution.py index 947bbd5d5..137683693 100644 --- a/examples/agents/39_local_code_execution.py +++ b/examples/agents/39_local_code_execution.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """First-class local code execution — agents that write and run code. Demonstrates three ways to enable code execution on an agent: @@ -15,8 +12,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, CodeExecutionConfig @@ -102,7 +99,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(simple_coder) # CLI alternative: - # agentspan deploy --package examples.39_local_code_execution + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(simple_coder) diff --git a/examples/agents/39a_docker_code_execution.py b/examples/agents/39a_docker_code_execution.py index 3f1786767..e1b587eb8 100644 --- a/examples/agents/39a_docker_code_execution.py +++ b/examples/agents/39a_docker_code_execution.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Docker-sandboxed code execution — run LLM-generated code in a container. The agent writes code and the ``DockerCodeExecutor`` runs it inside an @@ -10,7 +7,7 @@ Requirements: - Conductor server with LLM support - Docker installed and daemon running - - export AGENTSPAN_SERVER_URL=http://localhost:8080/api + - export CONDUCTOR_SERVER_URL=http://localhost:8080/api """ from conductor.ai.agents import Agent, AgentRuntime, CodeExecutionConfig @@ -48,7 +45,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(docker_coder) # CLI alternative: - # agentspan deploy --package examples.39a_docker_code_execution + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(docker_coder) diff --git a/examples/agents/39b_jupyter_code_execution.py b/examples/agents/39b_jupyter_code_execution.py index 46f93aa3d..9aa7bbd60 100644 --- a/examples/agents/39b_jupyter_code_execution.py +++ b/examples/agents/39b_jupyter_code_execution.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Jupyter kernel code execution — persistent state across calls. The ``JupyterCodeExecutor`` runs code in a real Jupyter kernel. Variables, @@ -11,7 +8,7 @@ Requirements: - Conductor server with LLM support - pip install jupyter_client ipykernel - - export AGENTSPAN_SERVER_URL=http://localhost:8080/api + - export CONDUCTOR_SERVER_URL=http://localhost:8080/api """ from conductor.ai.agents import Agent, AgentRuntime, CodeExecutionConfig @@ -52,7 +49,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(jupyter_coder) # CLI alternative: - # agentspan deploy --package examples.39b_jupyter_code_execution + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(jupyter_coder) diff --git a/examples/agents/39c_serverless_code_execution.py b/examples/agents/39c_serverless_code_execution.py index 82f385795..7794276eb 100644 --- a/examples/agents/39c_serverless_code_execution.py +++ b/examples/agents/39c_serverless_code_execution.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Serverless code execution — run code via a remote HTTP API. The ``ServerlessCodeExecutor`` sends code to an HTTP endpoint and returns @@ -18,7 +15,7 @@ Requirements: - Conductor server with LLM support - - export AGENTSPAN_SERVER_URL=http://localhost:8080/api + - export CONDUCTOR_SERVER_URL=http://localhost:8080/api """ import json @@ -99,7 +96,7 @@ def _start_mock_server(port: int = 9753) -> HTTPServer: # 1. Deploy once during CI/CD: # runtime.deploy(serverless_coder) # CLI alternative: - # agentspan deploy --package examples.39c_serverless_code_execution + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(serverless_coder) diff --git a/examples/agents/40_media_generation_agent.py b/examples/agents/40_media_generation_agent.py index caa20038d..afa974317 100644 --- a/examples/agents/40_media_generation_agent.py +++ b/examples/agents/40_media_generation_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """ Media Generation Agent — generate images, audio, and video using AI models. @@ -18,8 +15,8 @@ Requirements: - Conductor server with OpenAI integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, audio_tool, image_tool, video_tool @@ -85,7 +82,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(media_agent) # CLI alternative: - # agentspan deploy --package examples.40_media_generation_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(media_agent) diff --git a/examples/agents/41_sequential_pipeline_tools.py b/examples/agents/41_sequential_pipeline_tools.py index da1518d6a..2ff7262ca 100644 --- a/examples/agents/41_sequential_pipeline_tools.py +++ b/examples/agents/41_sequential_pipeline_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Sequential Pipeline with Stage-Level Tools — movie production pipeline. Demonstrates the sequential strategy where EACH sub-agent in the pipeline @@ -14,8 +11,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -211,7 +208,7 @@ def assemble_production(title: str, total_scenes: int, # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.41_sequential_pipeline_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/42_security_testing.py b/examples/agents/42_security_testing.py index f4c227f41..e67ec6034 100644 --- a/examples/agents/42_security_testing.py +++ b/examples/agents/42_security_testing.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Security Testing Pipeline — red-team evaluation of AI safety. Demonstrates a sequential pipeline for automated red-team security testing. @@ -18,8 +15,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -144,7 +141,7 @@ def score_safety(response_text: str, attack_category: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.42_security_testing + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/43_data_security_pipeline.py b/examples/agents/43_data_security_pipeline.py index 21c7304d3..32c699923 100644 --- a/examples/agents/43_data_security_pipeline.py +++ b/examples/agents/43_data_security_pipeline.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Data Security Pipeline — controlled data flow with redaction. Demonstrates a sequential pipeline with data flow control where @@ -17,8 +14,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import json @@ -141,7 +138,7 @@ def redact_sensitive_fields(data: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.43_data_security_pipeline + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/44_safety_guardrails.py b/examples/agents/44_safety_guardrails.py index 4dac603c0..09bffa7e9 100644 --- a/examples/agents/44_safety_guardrails.py +++ b/examples/agents/44_safety_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Safety Guardrails Pipeline — PII detection and sanitization. Demonstrates a sequential pipeline where a safety checker agent scans @@ -18,8 +15,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import re @@ -132,7 +129,7 @@ def sanitize_response(text: str, pii_types: str = "") -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.44_safety_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/45_agent_tool.py b/examples/agents/45_agent_tool.py index f2092e111..b69b63a3b 100644 --- a/examples/agents/45_agent_tool.py +++ b/examples/agents/45_agent_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent Tool — wrap an agent as a callable tool. Unlike sub-agents (which use handoff delegation), an agent_tool is invoked @@ -14,8 +11,8 @@ Requirements: - Conductor server with AgentTool support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, agent_tool, tool @@ -105,7 +102,7 @@ def calculate(expression: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(manager) # CLI alternative: - # agentspan deploy --package examples.45_agent_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(manager) diff --git a/examples/agents/46_transfer_control.py b/examples/agents/46_transfer_control.py index c30c6da7a..a96798252 100644 --- a/examples/agents/46_transfer_control.py +++ b/examples/agents/46_transfer_control.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Transfer Control — restrict which agents can hand off to which. Uses ``allowed_transitions`` to constrain handoff paths between sub-agents. @@ -9,8 +6,8 @@ Requirements: - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -110,7 +107,7 @@ def write_summary(findings: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.46_transfer_control + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/47_callbacks.py b/examples/agents/47_callbacks.py index 1ec39919d..a7551af77 100644 --- a/examples/agents/47_callbacks.py +++ b/examples/agents/47_callbacks.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Callbacks — lifecycle hooks before and after LLM calls. Demonstrates using ``before_model_callback`` and ``after_model_callback`` @@ -9,8 +6,8 @@ Requirements: - Conductor server with callback support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -92,7 +89,7 @@ def get_facts(topic: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.47_callbacks + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/48_planner.py b/examples/agents/48_planner.py index da964fec0..cb814873e 100644 --- a/examples/agents/48_planner.py +++ b/examples/agents/48_planner.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Planner — agent that plans before executing. When ``enable_planning=True``, the server enhances the system prompt with @@ -9,8 +6,8 @@ Requirements: - Conductor server with planner support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from settings import settings @@ -82,7 +79,7 @@ def write_section(title: str, content: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.48_planner + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/49_include_contents.py b/examples/agents/49_include_contents.py index bc3f430ba..5dde1267f 100644 --- a/examples/agents/49_include_contents.py +++ b/examples/agents/49_include_contents.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Include Contents — control context passed to sub-agents. When ``include_contents="none"``, a sub-agent starts with a clean slate @@ -10,8 +7,8 @@ Requirements: - Conductor server with include_contents support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -74,7 +71,7 @@ def summarize_text(text: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.49_include_contents + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/50_thinking_config.py b/examples/agents/50_thinking_config.py index 559c95c12..fef6caa07 100644 --- a/examples/agents/50_thinking_config.py +++ b/examples/agents/50_thinking_config.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Thinking Config — enable extended reasoning for complex tasks. When ``thinking_budget_tokens`` is set, the agent uses extended thinking @@ -11,8 +8,8 @@ Requirements: - Conductor server with thinking config support - A model that supports extended thinking (e.g., Claude with thinking) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -61,7 +58,7 @@ def calculate(expression: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.50_thinking_config + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/51_shared_state.py b/examples/agents/51_shared_state.py index 51a8bd99e..c1a66c778 100644 --- a/examples/agents/51_shared_state.py +++ b/examples/agents/51_shared_state.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Shared State — tools sharing state across calls via ToolContext. Tools can read and write to ``context.state``, a dictionary that persists @@ -10,8 +7,8 @@ Requirements: - Conductor server with state support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -90,7 +87,7 @@ def clear_list(context: ToolContext = None) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.51_shared_state + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/52_nested_strategies.py b/examples/agents/52_nested_strategies.py index b1f2a5d2d..01f5a067b 100644 --- a/examples/agents/52_nested_strategies.py +++ b/examples/agents/52_nested_strategies.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Nested Strategies — parallel agents inside a sequential pipeline. Demonstrates composing strategies: a ParallelAgent phase runs multiple @@ -10,8 +7,8 @@ Requirements: - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime @@ -69,7 +66,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.52_nested_strategies + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/53_agent_lifecycle_callbacks.py b/examples/agents/53_agent_lifecycle_callbacks.py index 170dba6a5..2bea8a84f 100644 --- a/examples/agents/53_agent_lifecycle_callbacks.py +++ b/examples/agents/53_agent_lifecycle_callbacks.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent Lifecycle Callbacks — composable handler classes. Demonstrates using ``CallbackHandler`` subclasses to hook into agent @@ -9,8 +6,8 @@ Requirements: - Conductor server with callback support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ import time @@ -87,7 +84,7 @@ def lookup_weather(city: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.53_agent_lifecycle_callbacks + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/54_software_bug_assistant.py b/examples/agents/54_software_bug_assistant.py index 94a56106a..5064d3421 100644 --- a/examples/agents/54_software_bug_assistant.py +++ b/examples/agents/54_software_bug_assistant.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Software Bug Assistant — agent_tool + mcp_tool for bug triage. Native SDK version of ADK example 33. Demonstrates: @@ -10,8 +7,8 @@ Requirements: - Conductor server with AgentTool + MCP support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment - GH_TOKEN in .env or environment """ @@ -269,7 +266,7 @@ def search_web(query: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(software_assistant) # CLI alternative: - # agentspan deploy --package examples.54_software_bug_assistant + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(software_assistant) diff --git a/examples/agents/55_ml_engineering.py b/examples/agents/55_ml_engineering.py index fc6719d62..5c607db92 100644 --- a/examples/agents/55_ml_engineering.py +++ b/examples/agents/55_ml_engineering.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """ML Engineering Pipeline — multi-agent ML workflow. Builds a five-stage pipeline: @@ -15,14 +12,14 @@ python 55_ml_engineering.py Requirements: - - Agentspan server running - - OPENAI_API_KEY stored: agentspan credentials set OPENAI_API_KEY + - Conductor server running + - OPENAI_API_KEY stored: the Conductor server credential store """ import os from conductor.ai.agents import Agent, AgentRuntime -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "anthropic/claude-sonnet-4-6") # ── Phase 1: Data Analysis ──────────────────────────────────────── @@ -99,7 +96,7 @@ # 1. Deploy once during CI/CD: # rt.deploy(ml_pipeline) # CLI alternative: - # agentspan deploy --package examples.55_ml_engineering + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(ml_pipeline) diff --git a/examples/agents/56_rag_agent.py b/examples/agents/56_rag_agent.py index 36ee4cb4a..abccf4c93 100644 --- a/examples/agents/56_rag_agent.py +++ b/examples/agents/56_rag_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """RAG Agent — vector search + document indexing. Native SDK version of ADK example 35. Demonstrates: @@ -16,8 +13,8 @@ Requirements: - Conductor server with RAG system tasks enabled (--spring.profiles.active=rag) - A configured vector database (e.g., pgvector) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, search_tool, index_tool @@ -214,7 +211,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(rag_agent) # CLI alternative: - # agentspan deploy --package examples.56_rag_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(rag_agent) diff --git a/examples/agents/57_plan_dry_run.py b/examples/agents/57_plan_dry_run.py index 29ec25d11..347e67464 100644 --- a/examples/agents/57_plan_dry_run.py +++ b/examples/agents/57_plan_dry_run.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Plan (Dry Run) — compile an agent without executing it. Demonstrates: @@ -14,8 +11,8 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ import json diff --git a/examples/agents/58_scatter_gather.py b/examples/agents/58_scatter_gather.py index bc84e670a..8e37e6c17 100644 --- a/examples/agents/58_scatter_gather.py +++ b/examples/agents/58_scatter_gather.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Scatter-Gather — massive parallel multi-agent orchestration. Demonstrates: @@ -14,7 +11,7 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment - AGENT_SECONDARY_LLM_MODEL=openai/gpt-4o in .env or environment """ @@ -134,7 +131,7 @@ def search_knowledge_base(query: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.58_scatter_gather + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/59_coding_agent.py b/examples/agents/59_coding_agent.py index 44c917c48..cc531e5f1 100644 --- a/examples/agents/59_coding_agent.py +++ b/examples/agents/59_coding_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Coding Agent with QA Tester — write, review, and fix code. Demonstrates: @@ -20,7 +17,7 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -102,7 +99,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(coder) # CLI alternative: - # agentspan deploy --package examples.59_coding_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coder) diff --git a/examples/agents/60_github_coding_agent.py b/examples/agents/60_github_coding_agent.py index ec7fef3ee..0ff32d2a9 100644 --- a/examples/agents/60_github_coding_agent.py +++ b/examples/agents/60_github_coding_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """GitHub Coding Agent — pick an issue, code the fix, create a PR. Demonstrates: @@ -26,7 +23,7 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment - gh CLI authenticated (gh auth status) - Git configured with push access to the repo """ @@ -39,7 +36,7 @@ from conductor.ai.agents.handoff import OnTextMention from conductor.ai.agents.tool import tool -REPO = "agentspan/codingexamples" +REPO = "Conductor/codingexamples" WORK_DIR = f"/tmp/codingexamples-{uuid.uuid4().hex[:8]}" @@ -407,7 +404,7 @@ def create_pull_request(title: str, body: str, issue_number: int = 0) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(coding_team) # CLI alternative: - # agentspan deploy --package examples.60_github_coding_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coding_team) diff --git a/examples/agents/60a_github_coding_agent_simple.py b/examples/agents/60a_github_coding_agent_simple.py index 86e5a3843..7454e0125 100644 --- a/examples/agents/60a_github_coding_agent_simple.py +++ b/examples/agents/60a_github_coding_agent_simple.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """GitHub Coding Agent (simplified) — pick an issue, code the fix, create a PR. Uses built-in code execution (local_code_execution=True) so the LLM @@ -27,7 +24,7 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment - gh CLI authenticated (gh auth status) - Git configured with push access to the repo """ @@ -37,7 +34,7 @@ from conductor.ai.agents import Agent, AgentRuntime, Strategy from conductor.ai.agents.handoff import OnTextMention -REPO = "agentspan/codingexamples" +REPO = "Conductor/codingexamples" WORK_DIR = f"/tmp/codingexamples-{uuid.uuid4().hex[:8]}" # ── GitHub Agent: handles all git/gh operations ────────────────────── @@ -202,7 +199,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(coding_team) # CLI alternative: - # agentspan deploy --package examples.60a_github_coding_agent_simple + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coding_team) diff --git a/examples/agents/61_github_coding_agent_chained.py b/examples/agents/61_github_coding_agent_chained.py index fe43a86f6..86a9b253f 100644 --- a/examples/agents/61_github_coding_agent_chained.py +++ b/examples/agents/61_github_coding_agent_chained.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """GitHub Coding Agent — issue to PR pipeline. Deploys and serves a three-stage pipeline: @@ -11,11 +8,11 @@ Run: python github_coding_agent.py # Deploy + serve - agentspan run github_pipeline "..." # Trigger (from another terminal) + Conductor run github_pipeline "..." # Trigger (from another terminal) Requirements: - - Agentspan server running - - GITHUB_TOKEN stored: agentspan credentials set GITHUB_TOKEN + - Conductor server running + - GITHUB_TOKEN stored: the Conductor server credential store - gh CLI installed """ @@ -24,7 +21,7 @@ from conductor.ai.agents.gate import TextGate from conductor.ai.agents.handoff import OnTextMention -REPO = "agentspan-ai/codingexamples" +REPO = "Conductor-ai/codingexamples" MODEL = "anthropic/claude-sonnet-4-6" @@ -184,7 +181,7 @@ def _pr_done(context: dict, **kwargs) -> bool: # 1. Deploy once during CI/CD: # rt.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.61_github_coding_agent_chained + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(pipeline) diff --git a/examples/agents/61a_github_coding_agent_claude_code.py b/examples/agents/61a_github_coding_agent_claude_code.py index 952916d25..f1958a024 100644 --- a/examples/agents/61a_github_coding_agent_claude_code.py +++ b/examples/agents/61a_github_coding_agent_claude_code.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """GitHub Coding Agent — Claude Code variant. Same issue-to-PR pipeline as 61, but replaces the SWARM coder/qa loop @@ -24,8 +21,8 @@ python 61a_github_coding_agent_claude_code.py Requirements: - - Agentspan server running - - GITHUB_TOKEN stored: agentspan credentials set GITHUB_TOKEN + - Conductor server running + - GITHUB_TOKEN stored: the Conductor server credential store - gh CLI installed - Claude Code SDK installed (pip install claude-code-sdk) """ @@ -34,7 +31,7 @@ from conductor.ai.agents.cli_config import CliConfig from conductor.ai.agents.gate import TextGate -REPO = "agentspan-ai/codingexamples" +REPO = "Conductor-ai/codingexamples" MODEL = "anthropic/claude-sonnet-4-6" @@ -185,7 +182,7 @@ def _pr_done(context: dict, **kwargs) -> bool: # 1. Deploy once during CI/CD: # rt.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.61a_github_coding_agent_claude_code + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(pipeline) diff --git a/examples/agents/62_cli_tool_guardrails.py b/examples/agents/62_cli_tool_guardrails.py index 67399d3d5..4e95b36f1 100644 --- a/examples/agents/62_cli_tool_guardrails.py +++ b/examples/agents/62_cli_tool_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """CLI tool with guardrails — safe command execution. Demonstrates tool-level guardrails on CLI commands. The agent can run @@ -25,7 +22,7 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment """ from settings import settings @@ -96,7 +93,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(ops_agent) # CLI alternative: - # agentspan deploy --package examples.62_cli_tool_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(ops_agent) diff --git a/examples/agents/62_coding_agent_openai.py b/examples/agents/62_coding_agent_openai.py index 166222f52..fcad79a4b 100644 --- a/examples/agents/62_coding_agent_openai.py +++ b/examples/agents/62_coding_agent_openai.py @@ -1,12 +1,9 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - -"""Coding Agent (OpenAI fallback) — a Claude Code alternative via Agentspan. +"""Coding Agent (OpenAI fallback) — a Claude Code alternative via Conductor. Use this when Claude Code is unavailable (outages, rate limits, etc.). It provides the same core workflow — read/edit files, run shell commands, execute code, review changes — but runs on OpenAI GPT-4o (or any provider you set via -AGENTSPAN_LLM_MODEL). +CONDUCTOR_AGENT_LLM_MODEL). Architecture: coder ↔ qa_reviewer (SWARM — LLM-driven handoffs) @@ -31,15 +28,15 @@ python 62_coding_agent_openai.py Environment variables: - AGENTSPAN_SERVER_URL — Agentspan server (default: http://localhost:8080/api) - AGENTSPAN_LLM_MODEL — override model (default: openai/gpt-4o) + CONDUCTOR_SERVER_URL — Conductor server (default: http://localhost:8080/api) + CONDUCTOR_AGENT_LLM_MODEL — override model (default: openai/gpt-4o) OPENAI_API_KEY — required for default OpenAI model CODING_AGENT_CWD — working directory for file ops (default: current dir) Requirements: - - Agentspan server running (agentspan server start) - - AGENTSPAN_SERVER_URL set - - OPENAI_API_KEY set (or AGENTSPAN_LLM_MODEL pointing to another provider) + - Conductor server running (Conductor server start) + - CONDUCTOR_SERVER_URL set + - OPENAI_API_KEY set (or CONDUCTOR_AGENT_LLM_MODEL pointing to another provider) """ from __future__ import annotations @@ -56,7 +53,7 @@ # ── Configuration ───────────────────────────────────────────────────────────── -MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o") +MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o") # Root directory that file tools operate within; agents see paths relative to it. WORKDIR = os.environ.get("CODING_AGENT_CWD", os.getcwd()) @@ -340,7 +337,7 @@ def search_code( def _banner() -> None: provider = MODEL.split("/")[0] if "/" in MODEL else MODEL print("=" * 60) - print(" Coding Agent (Agentspan fallback for Claude Code outages)") + print(" Coding Agent (Conductor fallback for Claude Code outages)") print(f" Model : {MODEL}") print(f" Workdir: {WORKDIR}") print("=" * 60) @@ -375,4 +372,4 @@ def _banner() -> None: # Production deployment pattern: # 1. Deploy once: runtime.deploy(coder) # 2. Serve workers: runtime.serve(coder) - # CLI: agentspan deploy --package examples.62_coding_agent_openai + # CLI: runtime.deploy(agent) from a release script diff --git a/examples/agents/63_deploy.py b/examples/agents/63_deploy.py index a77eee2e6..9bcd3c27c 100644 --- a/examples/agents/63_deploy.py +++ b/examples/agents/63_deploy.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Deploy — register agents on the server (CI/CD step). Demonstrates: @@ -18,8 +15,8 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -79,7 +76,7 @@ def check_status(service: str) -> str: # for info in results: # print(f"Deployed: {info.agent_name} -> {info.registered_name}") # CLI alternative: - # agentspan deploy --package examples.63_deploy + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(doc_assistant, ops_bot) diff --git a/examples/agents/63b_serve.py b/examples/agents/63b_serve.py index c95f8d90d..c4b7e7366 100644 --- a/examples/agents/63b_serve.py +++ b/examples/agents/63b_serve.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Serve — keep tool workers running as a persistent service. Demonstrates: @@ -21,8 +18,8 @@ Requirements: - Conductor server running - Agents already deployed (run 63_deploy.py first) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, tool @@ -80,7 +77,7 @@ def check_status(service: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(doc_assistant, ops_bot) # CLI alternative: - # agentspan deploy --package examples.63b_serve + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(doc_assistant, ops_bot) diff --git a/examples/agents/63c_run_by_name.py b/examples/agents/63c_run_by_name.py index 8341f9234..eca94bfdb 100644 --- a/examples/agents/63c_run_by_name.py +++ b/examples/agents/63c_run_by_name.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Run by Name — execute a pre-deployed agent via ``runtime.run()``. Demonstrates: @@ -12,7 +9,7 @@ - Conductor server running - Agent deployed (run 63_deploy.py first) - Workers running (run 63b_serve.py in another terminal) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment """ from conductor.ai.agents import AgentRuntime @@ -27,7 +24,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(...) # CLI alternative: - # agentspan deploy --package examples.63c_run_by_name + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(...) diff --git a/examples/agents/63d_serve_from_package.py b/examples/agents/63d_serve_from_package.py index e17014921..99b467ccf 100644 --- a/examples/agents/63d_serve_from_package.py +++ b/examples/agents/63d_serve_from_package.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Serve from Package — auto-discover and serve all agents in a package. Demonstrates: @@ -16,7 +13,7 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment - A Python package with Agent instances at module level """ @@ -61,7 +58,7 @@ def health_check() -> str: # 1. Deploy once during CI/CD: # runtime.deploy(monitoring_agent, *discover_agents(["myapp.agents"])) # CLI alternative: - # agentspan deploy --package examples.63d_serve_from_package + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(monitoring_agent, packages=["myapp.agents"]) diff --git a/examples/agents/63e_run_monitoring.py b/examples/agents/63e_run_monitoring.py index 9af81022c..cd2871f98 100644 --- a/examples/agents/63e_run_monitoring.py +++ b/examples/agents/63e_run_monitoring.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Run Monitoring Agent — trigger the monitoring agent deployed by 63d. Demonstrates: @@ -10,7 +7,7 @@ Requirements: - Conductor server running - 63d_serve_from_package.py running in another terminal - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment """ from conductor.ai.agents import AgentRuntime @@ -25,7 +22,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(...) # CLI alternative: - # agentspan deploy --package examples.63e_run_monitoring + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(...) diff --git a/examples/agents/64_swarm_with_tools.py b/examples/agents/64_swarm_with_tools.py index 38f8c906f..f44d73862 100644 --- a/examples/agents/64_swarm_with_tools.py +++ b/examples/agents/64_swarm_with_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Swarm with Tools — sub-agents have their own domain tools. Extends the basic swarm pattern (example 17) by giving each specialist @@ -15,8 +12,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, Strategy, tool @@ -112,7 +109,7 @@ def lookup_order(order_id: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(support) # CLI alternative: - # agentspan deploy --package examples.64_swarm_with_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(support) diff --git a/examples/agents/65_parallel_with_tools.py b/examples/agents/65_parallel_with_tools.py index 30dc2d26a..245054384 100644 --- a/examples/agents/65_parallel_with_tools.py +++ b/examples/agents/65_parallel_with_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Parallel Agents with Tools — each branch has its own tools. Extends the basic parallel pattern (example 07) by giving each parallel @@ -17,8 +14,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, Strategy, tool @@ -95,7 +92,7 @@ def lookup_order(order_id: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(analysis) # CLI alternative: - # agentspan deploy --package examples.65_parallel_with_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(analysis) diff --git a/examples/agents/66_handoff_to_parallel.py b/examples/agents/66_handoff_to_parallel.py index f205036cb..6a8e1b872 100644 --- a/examples/agents/66_handoff_to_parallel.py +++ b/examples/agents/66_handoff_to_parallel.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Handoff to Parallel — delegate to a multi-agent group. Demonstrates a parent agent that can hand off to either a single agent @@ -16,8 +13,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -112,7 +109,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.66_handoff_to_parallel + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/67_router_to_sequential.py b/examples/agents/67_router_to_sequential.py index be807ffa3..f4b8804e4 100644 --- a/examples/agents/67_router_to_sequential.py +++ b/examples/agents/67_router_to_sequential.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Router to Sequential — route to a pipeline sub-agent. Demonstrates a router that selects between a single agent (for quick @@ -20,8 +17,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, Strategy @@ -124,7 +121,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(team) # CLI alternative: - # agentspan deploy --package examples.67_router_to_sequential + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(team) diff --git a/examples/agents/68_context_condensation.py b/examples/agents/68_context_condensation.py index ce7b20f3a..28f8f907d 100644 --- a/examples/agents/68_context_condensation.py +++ b/examples/agents/68_context_condensation.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Context Condensation Stress Test — orchestrator + sub-agent, history condenses 3+ times. An orchestrator agent calls a ``deep_analyst`` sub-agent once per technology domain. @@ -26,7 +23,7 @@ --------------------------------------------- Add to ``server/src/main/resources/application.properties`` and restart:: - agentspan.default-context-window=10000 + Conductor.default-context-window=10000 Why: gpt-4o-mini has a 128 K context window; 25 × 800-token responses (~20 K tokens) would not overflow it naturally. Setting the window to 10 K forces @@ -35,9 +32,9 @@ agents that accumulate very large tool outputs. Requirements: - - Conductor server with LLM support + ``agentspan.default-context-window=10000`` - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - Conductor server with LLM support + ``Conductor.default-context-window=10000`` + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from conductor.ai.agents import Agent, AgentRuntime, agent_tool, tool @@ -382,7 +379,7 @@ def fetch_domain_data(domain: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(orchestrator) # CLI alternative: - # agentspan deploy --package examples.68_context_condensation + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(orchestrator) diff --git a/examples/agents/70_ce_support_agent.py b/examples/agents/70_ce_support_agent.py index a52317af5..9da22f409 100644 --- a/examples/agents/70_ce_support_agent.py +++ b/examples/agents/70_ce_support_agent.py @@ -3,7 +3,7 @@ Takes a Zendesk ticket number and investigates across Zendesk, JIRA, HubSpot, Notion (runbooks), and GitHub to produce a solution with a priority rating. -Required credentials (set via `agentspan credentials set `): `):> +Required credentials (set via `the Conductor server credential store`): `):> ZENDESK_SUBDOMAIN – e.g. "mycompany" ZENDESK_EMAIL – admin email for API auth ZENDESK_API_TOKEN – Zendesk API token @@ -18,7 +18,7 @@ NOTION_RUNBOOK_DB_ID – Database ID of the runbooks database in Notion GITHUB_TOKEN – GitHub personal access token - GITHUB_ORG – GitHub organization name (e.g. "agentspan-dev") + GITHUB_ORG – GitHub organization name (e.g. "Conductor-dev") Usage: diff --git a/examples/agents/71_api_tool.py b/examples/agents/71_api_tool.py index 3df18ef55..8c7548209 100644 --- a/examples/agents/71_api_tool.py +++ b/examples/agents/71_api_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """API Tool — auto-discover endpoints from OpenAPI, Swagger, or Postman specs. Demonstrates api_tool(), which points to an API spec and automatically @@ -23,15 +20,15 @@ # Or start with auth (requires storing the secret as a credential): mcp-testkit --transport http --auth - # Store credentials via CLI or Agentspan UI: - agentspan credentials set HTTP_TEST_API_KEY + # Store credentials via CLI or Conductor UI: + the Conductor server credential store Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable - mcp-testkit running on http://localhost:3001 (for examples 1-3, see setup above) - - For GitHub example: agentspan credentials set GITHUB_TOKEN ghp_xxx + - For GitHub example: the Conductor server credential store """ from conductor.ai.agents import Agent, AgentRuntime, api_tool, tool @@ -129,7 +126,7 @@ def calculate(expression: str) -> dict: # it needs. # # Before running: -# agentspan credentials set GITHUB_TOKEN ghp_xxxxxxxxxxxx +# the Conductor server credential store github = api_tool( url="https://api.github.com", @@ -171,7 +168,7 @@ def calculate(expression: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(math_agent) # CLI alternative: - # agentspan deploy --package examples.71_api_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(math_agent) diff --git a/examples/agents/72_client_reconnect.md b/examples/agents/72_client_reconnect.md index b8ebfa08f..077179daf 100644 --- a/examples/agents/72_client_reconnect.md +++ b/examples/agents/72_client_reconnect.md @@ -4,7 +4,7 @@ This demo proves that an agent execution survives a hard kill of the local SDK p ## Prerequisites -Start the Agentspan server with Docker Compose from the deployment branch or worktree: +Start a Conductor server with Docker Compose or the Conductor CLI: ```bash cd deployment/docker-compose @@ -26,8 +26,8 @@ pip install conductor-agent-sdk Set the server URL and model: ```bash -export AGENTSPAN_SERVER_URL=http://localhost:8080/api -export AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini +export CONDUCTOR_SERVER_URL=http://localhost:8080/api +export CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini ``` ## Terminal 1: Start the agent @@ -41,7 +41,7 @@ Wait for: ```text Agent is durably paused on the server. Now hard-kill this client from another terminal with: - python 72_client_reconnect.py kill-client --client-info-file /tmp/agentspan_client_reconnect.client.json + python 72_client_reconnect.py kill-client --client-info-file /tmp/conductor_agent_client_reconnect.client.json ``` ## Terminal 2: Hard-kill the client diff --git a/examples/agents/72_client_reconnect.py b/examples/agents/72_client_reconnect.py index 5bb530aa5..cec525d0f 100644 --- a/examples/agents/72_client_reconnect.py +++ b/examples/agents/72_client_reconnect.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Client Reconnect — hard-kill the SDK process and resume later. Demonstrates: @@ -11,12 +8,12 @@ - Reconnecting later by execution_id and continuing the same workflow This proves client-process durability. The local Python process can die, but -the workflow state remains stored on the Agentspan/Conductor server. +the workflow state remains stored on the Conductor/Conductor server. Requirements: - - Agentspan server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in environment - - AGENTSPAN_LLM_MODEL set (default: openai/gpt-4o-mini via settings.py) + - Conductor server running + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in environment + - CONDUCTOR_AGENT_LLM_MODEL set (default: openai/gpt-4o-mini via settings.py) - Provider API key configured on the server (for example OPENAI_API_KEY) """ @@ -33,8 +30,8 @@ from conductor.ai.agents import Agent, AgentRuntime, tool from settings import settings -DEFAULT_WORKFLOW_FILE = Path("/tmp/agentspan_client_reconnect.execution_id") -DEFAULT_CLIENT_INFO_FILE = Path("/tmp/agentspan_client_reconnect.client.json") +DEFAULT_WORKFLOW_FILE = Path("/tmp/Conductor_client_reconnect.execution_id") +DEFAULT_CLIENT_INFO_FILE = Path("/tmp/Conductor_client_reconnect.client.json") @tool(approval_required=True) @@ -89,7 +86,7 @@ def run_once(prompt: str) -> None: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.72_client_reconnect + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/73_worker_restart_recovery.md b/examples/agents/73_worker_restart_recovery.md index df207eb85..77785b41d 100644 --- a/examples/agents/73_worker_restart_recovery.md +++ b/examples/agents/73_worker_restart_recovery.md @@ -4,7 +4,7 @@ This demo proves that an agent execution survives worker-service outage and cont ## Prerequisites -Start the Agentspan server with Docker Compose from the deployment branch or worktree: +Start a Conductor server with Docker Compose or the Conductor CLI: ```bash cd deployment/docker-compose @@ -26,8 +26,8 @@ pip install conductor-agent-sdk Set the server URL and model: ```bash -export AGENTSPAN_SERVER_URL=http://localhost:8080/api -export AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini +export CONDUCTOR_SERVER_URL=http://localhost:8080/api +export CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini ``` ## Terminal 1: Deploy the agent definition @@ -42,7 +42,7 @@ python 73_worker_restart_recovery.py deploy python 73_worker_restart_recovery.py serve ``` -This writes the worker PID and process group to `/tmp/agentspan_worker_restart.worker.json`. +This writes the worker PID and process group to `/tmp/conductor_agent_worker_restart.worker.json`. ## Terminal 3: Kill the worker service @@ -72,7 +72,7 @@ python 73_worker_restart_recovery.py serve python 73_worker_restart_recovery.py status ``` -The attempt history file at `/tmp/agentspan_worker_restart.attempts.json` should eventually show: +The attempt history file at `/tmp/conductor_agent_worker_restart.attempts.json` should eventually show: - no attempts while the worker service is down - attempt 1 starts and completes after the worker service comes back diff --git a/examples/agents/73_worker_restart_recovery.py b/examples/agents/73_worker_restart_recovery.py index 816c5871d..aae1e1656 100644 --- a/examples/agents/73_worker_restart_recovery.py +++ b/examples/agents/73_worker_restart_recovery.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Worker Service Recovery — workflow waits durably while workers are down. Demonstrates: @@ -10,13 +7,13 @@ - Watching the same workflow complete after the worker service returns This proves worker-service durability. The workflow remains stored on the -Agentspan/Conductor server while Python tool workers are unavailable, and it +Conductor/Conductor server while Python tool workers are unavailable, and it continues when a worker service comes back online. Requirements: - - Agentspan server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in environment - - AGENTSPAN_LLM_MODEL set (default: openai/gpt-4o-mini via settings.py) + - Conductor server running + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in environment + - CONDUCTOR_AGENT_LLM_MODEL set (default: openai/gpt-4o-mini via settings.py) - Provider API key configured on the server (for example OPENAI_API_KEY) """ @@ -32,9 +29,9 @@ from conductor.ai.agents import Agent, AgentRuntime, tool from settings import settings -DEFAULT_WORKFLOW_FILE = Path("/tmp/agentspan_worker_restart.execution_id") -DEFAULT_WORKER_INFO_FILE = Path("/tmp/agentspan_worker_restart.worker.json") -DEFAULT_ATTEMPT_FILE = Path("/tmp/agentspan_worker_restart.attempts.json") +DEFAULT_WORKFLOW_FILE = Path("/tmp/Conductor_worker_restart.execution_id") +DEFAULT_WORKER_INFO_FILE = Path("/tmp/Conductor_worker_restart.worker.json") +DEFAULT_ATTEMPT_FILE = Path("/tmp/Conductor_worker_restart.attempts.json") def now_iso() -> str: @@ -129,7 +126,7 @@ def run_once() -> None: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.73_worker_restart_recovery + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/74_cli_error_output.py b/examples/agents/74_cli_error_output.py index cec00b9c8..4f0a586db 100644 --- a/examples/agents/74_cli_error_output.py +++ b/examples/agents/74_cli_error_output.py @@ -7,8 +7,8 @@ Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL (e.g. http://localhost:8080/api) - - AGENTSPAN_LLM_MODEL (e.g. openai/gpt-4o-mini) + - CONDUCTOR_SERVER_URL (e.g. http://localhost:8080/api) + - CONDUCTOR_AGENT_LLM_MODEL (e.g. openai/gpt-4o-mini) """ from conductor.ai.agents import Agent, AgentRuntime @@ -48,7 +48,7 @@ # 1. Deploy once during CI/CD: # rt.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.74_cli_error_output + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(agent) diff --git a/examples/agents/75_wait_for_message.py b/examples/agents/75_wait_for_message.py index 78ae57693..42c8b17fc 100644 --- a/examples/agents/75_wait_for_message.py +++ b/examples/agents/75_wait_for_message.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Wait for Message — continuously receive messages via Workflow Message Queue. Demonstrates: @@ -14,14 +11,14 @@ Requirements: - Conductor server with WMQ support (conductor.workflow-message-queue.enabled=true) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import os import time -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from conductor.ai.agents import Agent, AgentRuntime, wait_for_message_tool, tool from settings import settings diff --git a/examples/agents/76_wait_for_message_streaming.py b/examples/agents/76_wait_for_message_streaming.py index 4d5cf9632..7f925a09f 100644 --- a/examples/agents/76_wait_for_message_streaming.py +++ b/examples/agents/76_wait_for_message_streaming.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Wait for Message (Streaming) — send messages to a running agent and stream its responses. Demonstrates: @@ -13,16 +10,16 @@ drives the conversation by sending messages and reading streamed events. Requirements: - - AgentSpan server running at http://localhost:8080 - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - Conductor server running at http://localhost:8080 + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import os import threading import time -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from conductor.ai.agents import Agent, AgentRuntime, EventType, wait_for_message_tool, tool from settings import settings diff --git a/examples/agents/77_kafka_consumer_agent.py b/examples/agents/77_kafka_consumer_agent.py index 30f27b963..7ed72ddd9 100644 --- a/examples/agents/77_kafka_consumer_agent.py +++ b/examples/agents/77_kafka_consumer_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Kafka → Workflow Message Queue bridge — forward Kafka records to a running agent. Demonstrates: @@ -16,9 +13,9 @@ Requirements: - Kafka broker on localhost:9092 with topic le_random_topic - - AgentSpan server running at http://localhost:8080 - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - Conductor server running at http://localhost:8080 + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable - confluent-kafka (uv pip install confluent-kafka) """ @@ -29,7 +26,7 @@ KAFKA_BOOTSTRAP = "localhost:9092" KAFKA_TOPIC = "le_random_topic" -KAFKA_GROUP = "agentspan-echo-group" +KAFKA_GROUP = "Conductor-echo-group" @tool diff --git a/examples/agents/78_approval_workflow.py b/examples/agents/78_approval_workflow.py index 2275f22ce..91d9d7fb2 100644 --- a/examples/agents/78_approval_workflow.py +++ b/examples/agents/78_approval_workflow.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Approval Workflow — agent dynamically decides which tasks need human sign-off. Demonstrates: @@ -34,9 +31,9 @@ blocks on flag_for_approval until the operator responds. Requirements: - - AgentSpan server running at http://localhost:8080 - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - Conductor server running at http://localhost:8080 + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import json @@ -46,7 +43,7 @@ import time from pathlib import Path -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from conductor.ai.agents import Agent, AgentRuntime, tool, wait_for_message_tool from settings import settings diff --git a/examples/agents/79_agent_message_bus.py b/examples/agents/79_agent_message_bus.py index e31dcea69..554f1b3cb 100644 --- a/examples/agents/79_agent_message_bus.py +++ b/examples/agents/79_agent_message_bus.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent Message Bus — two agents communicating via Workflow Message Queue. Demonstrates: @@ -29,9 +26,9 @@ Researcher autonomously drives the Writer. Requirements: - - AgentSpan server running at http://localhost:8080 - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - Conductor server running at http://localhost:8080 + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import os @@ -40,7 +37,7 @@ import time from pathlib import Path -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from conductor.ai.agents import Agent, AgentRuntime, tool, wait_for_message_tool from settings import settings diff --git a/examples/agents/80_live_dashboard.py b/examples/agents/80_live_dashboard.py index a0166899d..8840383f1 100644 --- a/examples/agents/80_live_dashboard.py +++ b/examples/agents/80_live_dashboard.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Live Dashboard — a Feeder agent streams metrics into a Monitor agent in real time. This example shows how WMQ can be used as a live data channel between two @@ -38,9 +35,9 @@ content pipeline. Requirements: - - AgentSpan server running at http://localhost:8080 - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=anthropic/claude-sonnet-4-20250514 as environment variable + - Conductor server running at http://localhost:8080 + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=anthropic/claude-sonnet-4-20250514 as environment variable """ import json @@ -52,7 +49,7 @@ import time from pathlib import Path -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from settings import settings diff --git a/examples/agents/81_chat_repl.py b/examples/agents/81_chat_repl.py index 36333613b..4dcf4a4f2 100644 --- a/examples/agents/81_chat_repl.py +++ b/examples/agents/81_chat_repl.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Chat REPL — interactive conversation with a long-running agent via WMQ. This example turns a running agent into a conversational REPL. Every message @@ -44,9 +41,9 @@ bullet_split — split input into one bullet point per sentence Requirements: - - AgentSpan server running at http://localhost:8080 - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=anthropic/claude-sonnet-4-20250514 as environment variable + - Conductor server running at http://localhost:8080 + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=anthropic/claude-sonnet-4-20250514 as environment variable """ import argparse @@ -57,8 +54,8 @@ import time from pathlib import Path -# Keep conductor worker startup logs silent by default; set AGENTSPAN_LOG_LEVEL=INFO to see them. -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +# Keep conductor worker startup logs silent by default; set CONDUCTOR_LOG_LEVEL=INFO to see them. +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from settings import settings @@ -94,7 +91,7 @@ )), } -SESSION_FILE = Path("/tmp/agentspan_chat_repl.session") +SESSION_FILE = Path("/tmp/Conductor_chat_repl.session") # --------------------------------------------------------------------------- # Filesystem IPC setup diff --git a/examples/agents/82_coding_agent.py b/examples/agents/82_coding_agent.py index 830fb283a..e29a7fbd3 100644 --- a/examples/agents/82_coding_agent.py +++ b/examples/agents/82_coding_agent.py @@ -1,7 +1,4 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - -"""Coding Agent REPL — a filesystem-aware coding assistant backed by AgentSpan runtime. +"""Coding Agent REPL — a filesystem-aware coding assistant backed by Conductor runtime. This example is a Claude Code-style assistant you can actually use in a working session. It runs as a durable Conductor workflow, giving you things a local agent cannot: @@ -18,9 +15,9 @@ python 82_coding_agent.py --resume # resume last session Requirements: - - AgentSpan server running at http://localhost:8080 - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=anthropic/claude-sonnet-4-20250514 + - Conductor server running at http://localhost:8080 + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=anthropic/claude-sonnet-4-20250514 """ import argparse @@ -31,7 +28,7 @@ import threading from pathlib import Path -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from conductor.ai.agents import Agent, AgentRuntime, EventType, tool, wait_for_message_tool from settings import settings @@ -40,7 +37,7 @@ # Constants # --------------------------------------------------------------------------- -SESSION_FILE = Path("/tmp/agentspan_coding_agent.session") +SESSION_FILE = Path("/tmp/Conductor_coding_agent.session") _DEFAULT_SHELL_TIMEOUT = 30 # seconds per shell command _MAX_FILE_BYTES = 200_000 # 200 KB — refuse larger files in read_file _MAX_SHELL_OUTPUT = 8_000 # truncate shell output shown to the LLM diff --git a/examples/agents/82_fan_out_fan_in.py b/examples/agents/82_fan_out_fan_in.py index 04ae5d582..3fe654314 100644 --- a/examples/agents/82_fan_out_fan_in.py +++ b/examples/agents/82_fan_out_fan_in.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Fan-out / Fan-in — orchestrator broadcasts tasks to multiple worker agents, then collects and aggregates all results. @@ -32,8 +29,8 @@ aggregates the three answers into a side-by-side comparison report. Requirements: - - AgentSpan server running (CONDUCTOR_SERVER_URL / AGENTSPAN_SERVER_URL) - - AGENTSPAN_LLM_MODEL set to a working model + - Conductor server running (CONDUCTOR_SERVER_URL / CONDUCTOR_SERVER_URL) + - CONDUCTOR_AGENT_LLM_MODEL set to a working model """ import json diff --git a/examples/agents/82b_coding_agent_tui.py b/examples/agents/82b_coding_agent_tui.py index 9aa1a1d70..704041937 100644 --- a/examples/agents/82b_coding_agent_tui.py +++ b/examples/agents/82b_coding_agent_tui.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Coding Agent TUI — a filesystem-aware coding assistant with a split-pane terminal UI. Like 82_coding_agent.py, but with two improvements: @@ -19,9 +16,9 @@ python 82b_coding_agent_tui.py --cwd /path/to/repo Requirements: - - AgentSpan server running at http://localhost:8080 - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=openai/gpt-4o + - Conductor server running at http://localhost:8080 + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o """ import argparse @@ -34,7 +31,7 @@ from dataclasses import dataclass, field from pathlib import Path -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from prompt_toolkit import Application from prompt_toolkit.buffer import Buffer @@ -49,7 +46,7 @@ # Constants # --------------------------------------------------------------------------- -SESSION_FILE = Path("/tmp/agentspan_coding_agent_tui.session") +SESSION_FILE = Path("/tmp/Conductor_coding_agent_tui.session") _DEFAULT_SHELL_TIMEOUT = 30 _MAX_FILE_BYTES = 200_000 _MAX_SHELL_OUTPUT = 8_000 diff --git a/examples/agents/83_stateful_resume.py b/examples/agents/83_stateful_resume.py index 6bc0b708b..1afa55c5c 100644 --- a/examples/agents/83_stateful_resume.py +++ b/examples/agents/83_stateful_resume.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Stateful Agent Resume — reconnect to a running workflow after runtime restart. Demonstrates: @@ -32,8 +29,8 @@ Requirements: - Conductor server with WMQ support (conductor.workflow-message-queue.enabled=true) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import time @@ -41,7 +38,7 @@ from conductor.ai.agents import Agent, AgentRuntime, tool, wait_for_message_tool from settings import settings -SESSION_FILE = "/tmp/agentspan_stateful_resume.session" +SESSION_FILE = "/tmp/Conductor_stateful_resume.session" @tool diff --git a/examples/agents/84_deterministic_stop.py b/examples/agents/84_deterministic_stop.py index 79bf35a8e..be3c78a93 100644 --- a/examples/agents/84_deterministic_stop.py +++ b/examples/agents/84_deterministic_stop.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Deterministic Stop — exit an agent loop without LLM cooperation. Demonstrates: @@ -31,15 +28,15 @@ The LLM could ignore this. handle.stop() makes this unnecessary. Requirements: - - Agentspan server (with _stop_requested support in compiler) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - Conductor server (with _stop_requested support in compiler) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ import os import time -os.environ.setdefault("AGENTSPAN_LOG_LEVEL", "WARNING") +os.environ.setdefault("CONDUCTOR_LOG_LEVEL", "WARNING") from conductor.ai.agents import Agent, AgentRuntime, tool, wait_for_message_tool from settings import settings diff --git a/examples/agents/85_plan_execute_harness.py b/examples/agents/85_plan_execute_harness.py index 6a5ef3adf..e905fee4d 100644 --- a/examples/agents/85_plan_execute_harness.py +++ b/examples/agents/85_plan_execute_harness.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Plan-Execute Harness — deterministic execution of LLM-generated plans. Demonstrates Strategy.PLAN_EXECUTE: a planner agent produces a structured plan @@ -40,9 +37,9 @@ python 85_plan_execute_harness.py "Climate change mitigation strategies for 2030" Requirements: - - Agentspan server with PLAN_EXECUTE strategy support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-4o-mini) + - Conductor server with PLAN_EXECUTE strategy support + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-4o-mini) """ import json diff --git a/examples/agents/86_coding_agent.py b/examples/agents/86_coding_agent.py index 75bc287f2..7cf43c0c1 100644 --- a/examples/agents/86_coding_agent.py +++ b/examples/agents/86_coding_agent.py @@ -1,7 +1,4 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Coding Agent Harness — deterministic, plan-first file editing. Demonstrates Strategy.PLAN_EXECUTE with a single-agent harness (planner only, @@ -74,9 +71,9 @@ python 86_coding_agent.py "Fix the failing test in tests/test_math.py" Requirements: - - Agentspan server with PLAN_EXECUTE strategy support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL set (or defaults to openai/gpt-4o-mini) + - Conductor server with PLAN_EXECUTE strategy support + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL set (or defaults to openai/gpt-4o-mini) """ import os @@ -234,7 +231,7 @@ def write_coder_plan(content: str) -> str: # ── Executor tools — declared on the harness, called by the compiled plan ──── # The planner does NOT have these. They are declared on the ``coder`` harness -# via ``tools=`` so Agentspan registers their Conductor task definitions. +# via ``tools=`` so Conductor registers their Conductor task definitions. # The compiled plan calls them by name as SIMPLE tasks. @@ -381,7 +378,7 @@ def write_file(path: str, content: str) -> str: ) # The harness: PLAN_EXECUTE with planner only (no fallback). -# tools= declares the executor tools so Agentspan registers their task +# tools= declares the executor tools so Conductor registers their task # definitions; the compiled plan calls them as SIMPLE Conductor tasks. coder = Agent( name="coder", diff --git a/examples/agents/90_guardrail_e2e_tests.py b/examples/agents/90_guardrail_e2e_tests.py index 5c740e01f..330f7338b 100644 --- a/examples/agents/90_guardrail_e2e_tests.py +++ b/examples/agents/90_guardrail_e2e_tests.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Guardrail E2E Test Suite — full 3×3×3 matrix. Tests every combination of Position × Type × OnFail: @@ -51,8 +48,8 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL in .env or environment """ import sys diff --git a/examples/agents/91_slack_autofix_agent.py b/examples/agents/91_slack_autofix_agent.py index e73d96ff8..4f8a5970c 100644 --- a/examples/agents/91_slack_autofix_agent.py +++ b/examples/agents/91_slack_autofix_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Slack Auto-Fix Agent — monitors a Slack channel and auto-creates PRs for bug reports. Monitors a Slack channel for bug reports. When a message describes something @@ -18,13 +15,13 @@ └── pr_creator — creates branch + commit + PR Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=anthropic/claude-opus-4-6 (or gpt-4o) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=anthropic/claude-opus-4-6 (or gpt-4o) - SLACK_BOT_TOKEN=xoxb-... (Bot token with channels:read, channels:history) - SLACK_CHANNEL_ID=C... (Channel to monitor) - GITHUB_TOKEN=ghp_... (Token with repo write access) - REPO_PATH=/path/to/repo (Local path to the codebase) - - GITHUB_REPO=owner/repo (e.g. agentspan-ai/agentspan) + - GITHUB_REPO=owner/repo (e.g. Conductor-ai/Conductor) Usage: # Run once — picks up latest unprocessed bug report @@ -56,7 +53,7 @@ # ── State file — tracks last processed Slack message ─────────────────────── -STATE_FILE = Path("/tmp/agentspan_autofix_state.json") +STATE_FILE = Path("/tmp/Conductor_autofix_state.json") def _load_state() -> dict: @@ -335,7 +332,7 @@ def post_slack_reply(channel: str, thread_ts: str, message: str) -> str: model=settings.llm_model, tools=[search_codebase, read_file, run_git_command], instructions=""" -You are a senior engineer investigating a bug in the Agentspan codebase. +You are a senior engineer investigating a bug in the Conductor codebase. Given a bug description, you: 1. Search the codebase to find the relevant files diff --git a/examples/agents/92_openai_agents_compat.py b/examples/agents/92_openai_agents_compat.py index b68537706..355fd741d 100644 --- a/examples/agents/92_openai_agents_compat.py +++ b/examples/agents/92_openai_agents_compat.py @@ -1,15 +1,12 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agents SDK compatibility — drop-in Runner replacement. -Shows how to migrate an openai-agents script to Agentspan by changing +Shows how to migrate an openai-agents script to Conductor by changing one import line. Everything else stays identical. Before (runs directly against OpenAI): from agents import Runner -After (runs on Agentspan — durable, observable, scalable): +After (runs on Conductor — durable, observable, scalable): from conductor.ai import Runner The rest of the code — Agent definition, @function_tool decorators, @@ -22,20 +19,20 @@ from conductor.ai import Runner # ← change this one line from agents import Agent, function_tool # ← unchanged -Pattern B — use Agentspan for everything (no openai-agents dependency):: +Pattern B — use Conductor for everything (no openai-agents dependency):: from conductor.ai import Runner, function_tool from conductor.ai.agents import Agent Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=openai/gpt-4o (or anthropic/claude-opus-4-6) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o (or anthropic/claude-opus-4-6) Usage: # Pattern A (requires openai-agents installed: uv add openai-agents) python 92_openai_agents_compat.py --pattern a - # Pattern B (Agentspan only, no openai-agents needed) + # Pattern B (Conductor only, no openai-agents needed) python 92_openai_agents_compat.py --pattern b python 92_openai_agents_compat.py # default: pattern b """ @@ -75,10 +72,10 @@ def get_time(timezone: str) -> str: return f"Unknown timezone: {timezone}" -# ── Pattern B — pure Agentspan, no openai-agents dependency ──────────────── +# ── Pattern B — pure Conductor, no openai-agents dependency ──────────────── def run_pattern_b() -> None: - """Run using Agentspan's own Agent and function_tool (same result).""" + """Run using Conductor's own Agent and function_tool (same result).""" from conductor.ai import Runner from conductor.ai.agents import Agent from settings import settings @@ -100,7 +97,7 @@ def run_pattern_b() -> None: # ── Pattern A — keep openai-agents Agent/function_tool, swap only Runner ─── def run_pattern_a() -> None: - """Run with openai-agents Agent but Agentspan's Runner. + """Run with openai-agents Agent but Conductor's Runner. Requires: uv add openai-agents """ @@ -114,7 +111,7 @@ def run_pattern_a() -> None: # ── The ONE line you change ──────────────────────────────────────────── # from agents import Runner # ← original openai-agents import - from conductor.ai import Runner # ← drop-in Agentspan replacement + from conductor.ai import Runner # ← drop-in Conductor replacement @function_tool def get_weather(city: str) -> str: @@ -161,7 +158,7 @@ def get_time(timezone: str) -> str: "--pattern", choices=["a", "b"], default="b", - help="a = openai-agents Agent + Agentspan Runner; b = pure Agentspan (default)", + help="a = openai-agents Agent + Conductor Runner; b = pure Conductor (default)", ) args = parser.parse_args() diff --git a/examples/agents/93_openai_runner_hello_world.py b/examples/agents/93_openai_runner_hello_world.py index ce00e5091..33cd3416e 100644 --- a/examples/agents/93_openai_runner_hello_world.py +++ b/examples/agents/93_openai_runner_hello_world.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agents SDK migration — hello world. This is examples/basic/hello_world.py from the openai-agents SDK @@ -9,7 +6,7 @@ Before (runs directly against OpenAI): from agents import Runner -After (runs on Agentspan — durable, observable, scalable): +After (runs on Conductor — durable, observable, scalable): from conductor.ai import Runner The diff: @@ -20,8 +17,8 @@ Requirements: - uv add openai-agents - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=openai/gpt-4o (or any supported model) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o (or any supported model) Usage: python 93_openai_runner_hello_world.py @@ -33,7 +30,7 @@ # ── Only this line changes ────────────────────────────────────────────────── # from agents import Runner # ← original (runs directly on OpenAI) -from conductor.ai import Runner # ← agentspan (runs on Agentspan) +from conductor.ai import Runner # ← Conductor (runs on Conductor) # ─────────────────────────────────────────────────────────────────────────── diff --git a/examples/agents/94_openai_runner_tools.py b/examples/agents/94_openai_runner_tools.py index 977aaf9e0..51f6f8b6a 100644 --- a/examples/agents/94_openai_runner_tools.py +++ b/examples/agents/94_openai_runner_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agents SDK migration — function tools. This is examples/basic/tools.py from the openai-agents SDK @@ -9,7 +6,7 @@ Before (runs directly against OpenAI): from agents import Runner -After (runs on Agentspan — durable, observable, scalable): +After (runs on Conductor — durable, observable, scalable): from conductor.ai import Runner The diff: @@ -17,14 +14,14 @@ +from conductor.ai import Runner @function_tool decorators, Agent definition, and result.final_output -are completely unchanged. Agentspan executes each tool call as a durable +are completely unchanged. Conductor executes each tool call as a durable worker task — if the process crashes mid-run, execution resumes from the last successful tool call. Requirements: - uv add openai-agents - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=openai/gpt-4o + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o Usage: python 94_openai_runner_tools.py @@ -39,7 +36,7 @@ # ── Only this line changes ────────────────────────────────────────────────── # from agents import Runner # ← original (runs directly on OpenAI) -from conductor.ai import Runner # ← agentspan (runs on Agentspan) +from conductor.ai import Runner # ← Conductor (runs on Conductor) # ─────────────────────────────────────────────────────────────────────────── diff --git a/examples/agents/95_openai_runner_handoffs.py b/examples/agents/95_openai_runner_handoffs.py index 5d8624f73..659afbcdd 100644 --- a/examples/agents/95_openai_runner_handoffs.py +++ b/examples/agents/95_openai_runner_handoffs.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agents SDK migration — multi-agent handoffs. This is examples/agent_patterns/routing.py from the openai-agents SDK @@ -9,7 +6,7 @@ Before (runs directly against OpenAI): from agents import Runner -After (runs on Agentspan — durable, observable, scalable): +After (runs on Conductor — durable, observable, scalable): from conductor.ai import Runner The diff: @@ -17,13 +14,13 @@ +from conductor.ai import Runner Agent definitions, handoffs list, and the Runner.run() call are unchanged. -Agentspan records every handoff decision in the execution history — you can -replay the full agent-to-agent routing in the Agentspan UI. +Conductor records every handoff decision in the execution history — you can +replay the full agent-to-agent routing in the Conductor UI. Requirements: - uv add openai-agents - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=openai/gpt-4o + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o Usage: python 95_openai_runner_handoffs.py @@ -35,7 +32,7 @@ # ── Only this line changes ────────────────────────────────────────────────── # from agents import Runner # ← original (runs directly on OpenAI) -from conductor.ai import Runner # ← agentspan (runs on Agentspan) +from conductor.ai import Runner # ← Conductor (runs on Conductor) # ─────────────────────────────────────────────────────────────────────────── french_agent = Agent( diff --git a/examples/agents/96_openai_runner_streaming.py b/examples/agents/96_openai_runner_streaming.py index 0c850ec19..62748e645 100644 --- a/examples/agents/96_openai_runner_streaming.py +++ b/examples/agents/96_openai_runner_streaming.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agents SDK migration — streaming. This is examples/basic/stream_text.py from the openai-agents SDK @@ -9,14 +6,14 @@ Before (runs directly against OpenAI): from agents import Runner -After (runs on Agentspan — durable, observable, scalable): +After (runs on Conductor — durable, observable, scalable): from conductor.ai import Runner The diff: -from agents import Runner +from conductor.ai import Runner -Agentspan's streaming model differs from openai-agents in that it streams +Conductor's streaming model differs from openai-agents in that it streams *execution events* (LLM calls, tool calls, results) rather than tokens. The final response arrives in the "done" event's output field. @@ -30,8 +27,8 @@ Requirements: - uv add openai-agents - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=openai/gpt-4o + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o Usage: python 96_openai_runner_streaming.py @@ -43,7 +40,7 @@ # ── Only this line changes ────────────────────────────────────────────────── # from agents import Runner # ← original (runs directly on OpenAI) -from conductor.ai import Runner # ← agentspan (runs on Agentspan) +from conductor.ai import Runner # ← Conductor (runs on Conductor) # ─────────────────────────────────────────────────────────────────────────── @@ -55,8 +52,8 @@ async def main(): stream = await Runner.run_streamed(agent, input="Please tell me 5 jokes.") - # Iterate Agentspan AgentEvent objects as they arrive from the server. - # Agentspan streams execution events — the final answer is in the "done" event. + # Iterate Conductor AgentEvent objects as they arrive from the server. + # Conductor streams execution events — the final answer is in the "done" event. async for event in stream: if event.type == "thinking" and event.content: # Show which task is running (LLM or tool name) diff --git a/examples/agents/97_openai_runner_sandbox.py b/examples/agents/97_openai_runner_sandbox.py index c95194023..a8a0885ef 100644 --- a/examples/agents/97_openai_runner_sandbox.py +++ b/examples/agents/97_openai_runner_sandbox.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agents SDK migration — sandbox agent (Docker). This is examples/sandbox/basic.py from the openai-agents SDK @@ -9,7 +6,7 @@ Before (runs directly against OpenAI): from agents import Runner -After (runs on Agentspan — durable, observable, scalable): +After (runs on Conductor — durable, observable, scalable): from conductor.ai import Runner The diff: @@ -17,21 +14,21 @@ +from conductor.ai import Runner Sandbox agents run code in an isolated Docker environment. The model can -inspect a workspace (files, directories) using a shell tool. With AgentspanRunner: - - Every shell command the model executes is recorded in Agentspan - - The full sandbox session is visible in the Agentspan UI - - If the process crashes, the Agentspan execution history is preserved +inspect a workspace (files, directories) using a shell tool. With ConductorRunner: + - Every shell command the model executes is recorded in Conductor + - The full sandbox session is visible in the Conductor UI + - If the process crashes, the Conductor execution history is preserved Architecture: SandboxAgent — openai-agents sandbox agent (file inspection via shell) Docker — isolated container with the workspace files - AgentspanRunner — routes execution through Agentspan instead of OpenAI directly + ConductorRunner — routes execution through Conductor instead of OpenAI directly Requirements: - uv add openai-agents - Docker running locally (docker ps should work) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api - - AGENTSPAN_LLM_MODEL=openai/gpt-4o + - CONDUCTOR_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o Usage: python 97_openai_runner_sandbox.py @@ -68,7 +65,7 @@ # ── Only this line changes ────────────────────────────────────────────────── # from agents import Runner # ← original (runs directly on OpenAI) -from conductor.ai import Runner # ← agentspan (runs on Agentspan) +from conductor.ai import Runner # ← Conductor (runs on Conductor) # ─────────────────────────────────────────────────────────────────────────── DEFAULT_QUESTION = "Summarize this project in 2 sentences." @@ -82,7 +79,7 @@ def _build_manifest() -> Manifest: "README.md": File( content=( b"# Demo Project\n\n" - b"A tiny demo project for the Agentspan sandbox runner example.\n" + b"A tiny demo project for the Conductor sandbox runner example.\n" b"The model can inspect files through the shell tool.\n" ) ), @@ -146,13 +143,13 @@ async def main(model: str, question: str) -> None: try: async with sandbox: - # Run the agent — AgentspanRunner.run_streamed() is a drop-in for Runner.run_streamed() + # Run the agent — ConductorRunner.run_streamed() is a drop-in for Runner.run_streamed() stream = await Runner.run_streamed( agent, question, run_config=RunConfig( sandbox=SandboxRunConfig(session=sandbox), - workflow_name="Agentspan Docker sandbox example", + workflow_name="Conductor Docker sandbox example", ), ) @@ -171,13 +168,13 @@ async def main(model: str, question: str) -> None: result = await stream.get_result() print(f"\n\nExecution ID: {result.execution_id}") - print("(View full run in the Agentspan UI)") + print("(View full run in the Conductor UI)") finally: await docker_client.delete(sandbox) if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Agentspan sandbox agent (Docker)") + parser = argparse.ArgumentParser(description="Conductor sandbox agent (Docker)") parser.add_argument("--model", default=DEFAULT_MODEL, help="LLM model to use") parser.add_argument("--question", default=DEFAULT_QUESTION, help="Question to ask") args = parser.parse_args() diff --git a/examples/agents/README.md b/examples/agents/README.md index 5a68f0bc1..41bae16ef 100644 --- a/examples/agents/README.md +++ b/examples/agents/README.md @@ -1,348 +1,28 @@ -# Examples +# Conductor-agent examples -Runnable examples demonstrating every feature of the Agentspan SDK. +Runnable Python examples for durable Conductor agents. Start a server, configure +the provider integration on that server, then use the canonical environment names: ---- - -## Examples vs. Production - -> **Every example uses `runtime.run()` for convenience. In production, you should not.** - -Examples call `runtime.run()` so you can try them in a single command — no setup, no -separate processes. But `run()` blocks the caller until the agent finishes, which is fine -for demos but not how you deploy real agents. - -### Production: Deploy → Serve → Run - -In production, the three concerns are separated: - -``` -┌──────────────────────────────────────────────────────────────┐ -│ 1. DEPLOY (once, during CI/CD) │ -│ Registers the agent definition with the Agentspan server │ -│ │ -│ runtime.deploy(agent) │ -│ # or CLI: agentspan deploy --package my_agents │ -├──────────────────────────────────────────────────────────────┤ -│ 2. SERVE (long-running worker process) │ -│ Listens for tool-call tasks and executes them │ -│ │ -│ runtime.serve(agent) │ -│ # typically run as a daemon, container, or systemd unit │ -├──────────────────────────────────────────────────────────────┤ -│ 3. RUN (on-demand, from anywhere) │ -│ Triggers an agent execution │ -│ │ -│ agentspan run "prompt" │ -│ # or SDK: runtime.run("agent_name", "prompt") │ -│ # or REST API │ -└──────────────────────────────────────────────────────────────┘ -``` - -Every example includes the deploy/serve pattern as commented code at the bottom of its -`__main__` block — look for the `# Production pattern:` comment. - -See [63_deploy.py](63_deploy.py), [63b_serve.py](63b_serve.py), and -[63c_run_by_name.py](63c_run_by_name.py) for a complete working example of this pattern. - ---- - -## Getting Started - -### 1. Install dependencies - -The core examples (numbered files in this directory) only need the `conductor-agent-sdk` package: - -```bash -uv pip install conductor-agent-sdk +```shell +export CONDUCTOR_SERVER_URL=http://localhost:8080/api +export CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini ``` -Framework-specific examples require additional packages. Install only what you need: - -#### LangChain examples (`langchain/`) - -```bash -uv pip install langchain langchain-core langchain-openai -``` - -| Package | Required | Notes | -|---------|----------|-------| -| `langchain` | Yes | Core framework, includes `create_agent` | -| `langchain-core` | Yes | Tools, prompts, output parsers, messages | -| `langchain-openai` | Yes | `ChatOpenAI` LLM provider | -| `pydantic` | Some examples | Used for structured output (03, 04, 24, 25) | - -#### LangGraph examples (`langgraph/`) - -```bash -uv pip install langgraph langchain-core langchain-openai -``` - -| Package | Required | Notes | -|---------|----------|-------| -| `langgraph` | Yes | `StateGraph`, `create_react_agent`, prebuilt nodes | -| `langchain-core` | Yes | Messages, tools, documents | -| `langchain-openai` | Yes | `ChatOpenAI` LLM provider | -| `langchain-anthropic` | Optional | Only for `43_react_agent_multi_model.py` (requires `ANTHROPIC_API_KEY`) | -| `pydantic` | Some examples | Used for structured output (08) | - -#### OpenAI Agents SDK examples (`openai/`) - -```bash -uv pip install openai-agents -``` - -| Package | Required | Notes | -|---------|----------|-------| -| `openai-agents` | Yes | `Agent`, `function_tool`, `ModelSettings`, guardrails | -| `pydantic` | Some examples | Used for structured output (03) | - -Requires `OPENAI_API_KEY` environment variable. - -#### Google ADK examples (`adk/`) - -```bash -uv pip install google-adk -``` - -| Package | Required | Notes | -|---------|----------|-------| -| `google-adk` | Yes | `Agent`, `SequentialAgent`, `ParallelAgent`, `LoopAgent`, planners | -| `pydantic` | Some examples | Used for structured output (03) | - -Requires `GOOGLE_GEMINI_API_KEY` environment variable. - -#### Install everything - -To install all framework dependencies at once: - -```bash -uv pip install langchain langchain-core langchain-openai langgraph openai-agents google-adk -``` - -### 2. Configure your environment - -Export environment variables: - -```bash -export AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini -export AGENTSPAN_SERVER_URL=http://localhost:8080/api -# export AGENTSPAN_AUTH_KEY= # if authentication is enabled -# export AGENTSPAN_AUTH_SECRET= -``` - -#### 2.1. Choose a model - -The `AGENTSPAN_LLM_MODEL` variable uses the `provider/model-name` format. Examples: - -| Provider | Model string | API key env var | -|----------|-------------|-----------------| -| OpenAI | `anthropic/claude-sonnet-4-6` (default) | `OPENAI_API_KEY` | -| Anthropic | `anthropic/claude-sonnet-4-20250514` | `ANTHROPIC_API_KEY` | -| Google Gemini | `google_gemini/gemini-2.0-flash` | `GOOGLE_GEMINI_API_KEY` | -| AWS Bedrock | `aws_bedrock/...` | AWS credentials | -| Azure OpenAI | `azure_openai/...` | Azure credentials | - -All supported providers: `openai`, `anthropic`, `google_gemini`, `google_vertex_ai`, -`azure_openai`, `aws_bedrock`, `cohere`, `mistral`, `groq`, `perplexity`, -`hugging_face`, `deepseek`. - -### 3. Run an example - -```bash -# Core SDK examples -python examples/01_basic_agent.py -python examples/15_agent_discussion.py - -# Framework-specific examples -python examples/langchain/01_hello_world.py -python examples/langgraph/01_hello_world.py -python examples/openai/01_basic_agent.py -python examples/adk/01_basic_agent.py -``` - ---- - -## Basic Examples - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 01 | [Basic Agent](01_basic_agent.py) | Simplest possible agent — single LLM, no tools, 5 lines of code | -| 02 | [Tools](02_tools.py) | Multiple `@tool` functions, approval-required tools | - -## Tool Calling - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 02a | [Simple Tools](02a_simple_tools.py) | Two tools (weather, stocks) — LLM picks the right one | -| 02b | [Multi-Step Tools](02b_multi_step_tools.py) | Chained tool calls: lookup → fetch → calculate → answer | -| 03 | [Structured Output](03_structured_output.py) | Pydantic `output_type` for typed, validated responses | -| 04 | [HTTP & MCP Tools](04_http_and_mcp_tools.py) | Server-side tools via `http_tool()` and `mcp_tool()` — no workers needed | -| 04b | [MCP Weather](04_mcp_weather.py) | Real-time weather via an MCP server | -| 14 | [Existing Workers](14_existing_workers.py) | Use existing `@worker_task` functions directly as agent tools | -| 33 | [Single Turn Tool](33_single_turn_tool.py) | Single-turn tool invocation with immediate response | -| 33 | [External Workers](33_external_workers.py) | Reference workers in other services via `@tool(external=True)` — no local code needed | - -## Multi-Agent Orchestration - -| # | Example | Pattern | Key API | -|---|---------|---------|---------| -| 05 | [Handoffs](05_handoffs.py) | LLM-driven delegation to sub-agents | `strategy="handoff"` | -| 06 | [Sequential Pipeline](06_sequential_pipeline.py) | Agents run in order, output chains forward | `strategy="sequential"`, `>>` operator | -| 07 | [Parallel Agents](07_parallel_agents.py) | All agents run concurrently, results aggregated | `strategy="parallel"` | -| 08 | [Router Agent](08_router_agent.py) | Router (Agent or callable) selects which sub-agent runs | `strategy="router"` | -| 13 | [Hierarchical Agents](13_hierarchical_agents.py) | 3-level nested hierarchy: CEO → leads → specialists | Nested `strategy="handoff"` | -| 15 | [Agent Discussion](15_agent_discussion.py) | Round-robin debate between agents, piped to a summarizer | `strategy="round_robin"`, `>>` | -| 16 | [Random Strategy](16_random_strategy.py) | Random agent selected each turn (brainstorming) | `strategy="random"` | -| 17 | [Swarm Orchestration](17_swarm_orchestration.py) | Automatic transitions via handoff conditions | `strategy="swarm"`, `OnTextMention` | -| 18 | [Manual Selection](18_manual_selection.py) | Human picks which agent speaks each turn | `strategy="manual"` | -| 20 | [Constrained Transitions](20_constrained_transitions.py) | Restrict which agents can follow which | `allowed_transitions` | -| 29 | [Agent Introductions](29_agent_introductions.py) | Agents introduce themselves before a group discussion | `introduction` parameter | -| 38 | [Tech Trends](38_tech_trends.py) | Multi-agent research pipeline with live HTTP API tools | `>>` operator, `from __future__ import annotations` | - -## Human-in-the-Loop - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 09 | [Human-in-the-Loop](09_human_in_the_loop.py) | Tool approval gate — approve or reject before execution | `approval_required=True` | -| 09b | [HITL with Feedback](09b_hitl_with_feedback.py) | Custom feedback via `respond()` — editorial review with revision notes | `handle.respond()` | -| 09c | [HITL with Streaming](09c_hitl_streaming.py) | Real-time event stream with approval pauses | `stream()` + `approve()` | - -## Guardrails & Safety - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 10 | [Guardrails](10_guardrails.py) | Output validation with `@guardrail` decorator, `OnFail`/`Position` enums | `@guardrail`, `OnFail`, `Position` | -| 21 | [Regex Guardrails](21_regex_guardrails.py) | Pattern-based blocking (emails, SSNs) and allow-listing (JSON) | `RegexGuardrail` | -| 22 | [LLM Guardrails](22_llm_guardrails.py) | AI-powered content safety evaluation via a judge LLM | `LLMGuardrail` | -| 31 | [Tool Guardrails](31_tool_guardrails.py) | Pre-execution validation on tool inputs (SQL injection blocking) | `@tool(guardrails=[...])` | -| 32 | [Human Guardrail](32_human_guardrail.py) | Pause agent for human review when output fails validation | `on_fail="human"` | -| 35 | [Standalone Guardrails](35_standalone_guardrails.py) | Use `@guardrail` as plain callables — no agent, no server needed | `@guardrail`, `GuardrailResult` | -| 36 | [Simple Agent Guardrails](36_simple_agent_guardrails.py) | Guardrails on agents without tools — mixed regex (InlineTask) + custom (worker) | `RegexGuardrail`, `@guardrail` | -| 37 | [Fix Guardrail](37_fix_guardrail.py) | Auto-correct output instead of retrying — deterministic fixes | `on_fail="fix"`, `fixed_output` | - -## Termination Conditions - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 19 | [Composable Termination](19_composable_termination.py) | Text mention, stop message, max messages, token budget, AND/OR composition | `TextMentionTermination`, `&`, `\|` | - -## Code Execution - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 24 | [Code Execution](24_code_execution.py) | Local, Docker, Jupyter, and serverless code execution sandboxes | `LocalCodeExecutor`, `DockerCodeExecutor` | - -## Memory - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 25 | [Semantic Memory](25_semantic_memory.py) | Long-term memory with similarity-based retrieval across sessions | `SemanticMemory` | - -## Observability - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 23 | [Token Tracking](23_token_tracking.py) | Per-run token usage and cost estimation | `result.token_usage` | -| 26 | [OpenTelemetry Tracing](26_opentelemetry_tracing.py) | Industry-standard OTel spans for runs, tools, and handoffs | `tracing` module | - -## Execution Modes - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 11 | [Streaming](11_streaming.py) | Default `runtime.run()` flow with a commented `runtime.stream()` alternative for real-time events | `runtime.run()`, `AgentEvent`, `EventType` | -| 12 | [Long-Running](12_long_running.py) | Default `runtime.run()` flow with a commented `runtime.start()` alternative for async polling | `runtime.run()`, `runtime.start()`, `handle.get_status()` | -| 72 | [Client Reconnect](72_client_reconnect.py) | Default `runtime.run()` flow plus an advanced reconnect demo that resumes the same execution after client death | `runtime.run()`, `runtime.start()`, `runtime.get_status()`, `runtime.respond()` | -| 73 | [Worker Restart Recovery](73_worker_restart_recovery.py) | Default `runtime.run()` flow plus an advanced deploy/serve/start recovery demo | `runtime.run()`, `runtime.deploy()`, `runtime.serve()`, `runtime.start()` | - -## Multimodal - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 30 | [Multimodal Agent](30_multimodal_agent.py) | Image/video analysis with vision models via the `media` parameter | `media=["url"]` | - -## Integrations - -| # | Example | What it demonstrates | -|---|---------|---------------------| -| 28 | [GPT Assistant Agent](28_gpt_assistant_agent.py) | Wrap OpenAI Assistants API (with code interpreter) as a Conductor agent | `GPTAssistantAgent` | - ---- - -## Troubleshooting - -### SSL Certificate Errors on macOS - -Examples that make outbound HTTPS calls (e.g., `38_tech_trends.py`) may fail with: -``` -[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate -``` - -This happens because macOS Python framework installs do not link to system certificates. -Fix by running (once per Python installation): - -```bash -# Replace 3.12 with your Python version -/Applications/Python\ 3.12/Install\ Certificates.command -``` - -### PEP 563 Compatibility - -Tool functions defined in modules that use `from __future__ import annotations` work -correctly. The SDK resolves string annotations to real types at registration time. +## Start here -## Feature Index +| Example | Demonstrates | +|---|---| +| `01_basic_agent.py` | A minimal agent and `runtime.run()`. | +| `02a_simple_tools.py` | Python worker tools. | +| `05_handoffs.py` | Agent handoffs. | +| `09_human_in_the_loop.py` | Approval and resume. | +| `57_plan_dry_run.py` | Compile without running. | +| `63b_serve.py` | Deploy and serve workers. | -Quick lookup — find the right example for any SDK feature: +Run an example from this directory: `python 01_basic_agent.py`. For production, +deploy with `AgentRuntime.deploy()` and run workers with `AgentRuntime.serve()`; +the Conductor CLI manages the server with `conductor server start`. -| Feature | Example(s) | -|---------|-----------| -| `Agent` | 01 | -| `@tool` decorator | 02, 02a, 02b | -| `http_tool()` | 04 | -| `mcp_tool()` | 04, 04b | -| `output_type` (Pydantic) | 03 | -| `strategy="handoff"` | 05, 13 | -| `strategy="sequential"`, `>>` | 06, 15 | -| `strategy="parallel"` | 07 | -| `strategy="router"` | 08 | -| `strategy="round_robin"` | 15, 20, 29 | -| `strategy="random"` | 16 | -| `strategy="swarm"` | 17 | -| `strategy="manual"` | 18 | -| `allowed_transitions` | 20 | -| `introduction` | 29 | -| `approval_required=True` | 02, 09 | -| `handle.approve()` / `reject()` | 09 | -| `handle.respond()` / `send()` | 09b, 27 | -| `runtime.run()` | 01, 02, 11, 12, 72, 73 | -| `runtime.stream()` | 09c, 11 | -| `runtime.start()` | 12, 18, 27, 72, 73 | -| `@guardrail` decorator | 10, 35 | -| `Guardrail` | 10, 32 | -| `OnFail` / `Position` enums | 10 | -| `RegexGuardrail` | 21 | -| `LLMGuardrail` | 22 | -| `on_fail="fix"` | 37 | -| `on_fail="human"` | 32 | -| `fixed_output` | 37 | -| `@tool(guardrails=[...])` | 31 | -| `TextMentionTermination` | 19 | -| `StopMessageTermination` | 19 | -| `MaxMessageTermination` | 19 | -| `TokenUsageTermination` | 19 | -| `&` / `\|` (composable) | 19 | -| `LocalCodeExecutor` | 24 | -| `DockerCodeExecutor` | 24 | -| `JupyterCodeExecutor` | 24 | -| `ServerlessCodeExecutor` | 24 | -| `SemanticMemory` | 25 | -| `TokenUsage` | 23 | -| OpenTelemetry tracing | 26 | -| `GPTAssistantAgent` | 28 | -| `@worker_task` as tools | 14 | -| `@tool(external=True)` | 33 | -| `OnTextMention` / `OnToolResult` | 17 | -| `media` (multimodal input) | 30 | -| `PromptTemplate` | kitchen_sink | -| `from __future__ import annotations` | 38 | +Framework-specific examples are in [ADK](adk/README.md), +[LangGraph](langgraph/README.md), and [OpenAI Agents SDK](openai/README.md). +Review tool side effects before using real credentials. diff --git a/examples/agents/_issue_fixer_instructions.py b/examples/agents/_issue_fixer_instructions.py index cbd154af2..387a93ba4 100644 --- a/examples/agents/_issue_fixer_instructions.py +++ b/examples/agents/_issue_fixer_instructions.py @@ -147,7 +147,7 @@ "issue_title": "", "change_type": "bug_fix" or "feature", "date": "<YYYY-MM-DD>", - "author": "agentspan-bot", + "author": "Conductor-bot", "root_cause": "<what was broken and why>", "what_changed": [ {{"file": "<path>", "change": "<what was modified and why>"}} diff --git a/examples/agents/_issue_fixer_tools.py b/examples/agents/_issue_fixer_tools.py index 61b3ce6fa..3f32c0707 100644 --- a/examples/agents/_issue_fixer_tools.py +++ b/examples/agents/_issue_fixer_tools.py @@ -766,7 +766,7 @@ def get_text(self): return "".join(self._texts) try: - req = urllib.request.Request(url, headers={"User-Agent": "AgentSpan-IssueFixer/1.0"}) + req = urllib.request.Request(url, headers={"User-Agent": "Conductor-IssueFixer/1.0"}) with urllib.request.urlopen(req, timeout=30) as resp: content_type = resp.headers.get("Content-Type", "") raw = resp.read(500_000).decode("utf-8", errors="replace") diff --git a/examples/agents/adk/00_hello_world.py b/examples/agents/adk/00_hello_world.py index cec1c1041..233028248 100644 --- a/examples/agents/adk/00_hello_world.py +++ b/examples/agents/adk/00_hello_world.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Minimal Google ADK greeting agent — for debugging the native runner. The simplest possible ADK agent: no tools, no structured output, one turn. @@ -10,8 +7,8 @@ Requirements: - pip install google-adk - GOOGLE_API_KEY or GEMINI_API_KEY environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash (for AgentSpan runs) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api (for AgentSpan runs) + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash (for Conductor runs) + - CONDUCTOR_SERVER_URL=http://localhost:8080/api (for Conductor runs) """ from google.adk.agents import Agent @@ -36,7 +33,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.00_hello_world + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/01_basic_agent.py b/examples/agents/adk/01_basic_agent.py index c6f34c697..470f823ff 100644 --- a/examples/agents/adk/01_basic_agent.py +++ b/examples/agents/adk/01_basic_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Basic Google ADK Agent — simplest possible agent. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -39,7 +36,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.01_basic_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/02_function_tools.py b/examples/agents/adk/02_function_tools.py index 25ced00a2..ac007d1b4 100644 --- a/examples/agents/adk/02_function_tools.py +++ b/examples/agents/adk/02_function_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Agent with Function Tools — tool calling via Python functions. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -103,7 +100,7 @@ def get_time_zone(city: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.02_function_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/03_structured_output.py b/examples/agents/adk/03_structured_output.py index 2e7b51b78..65ced7081 100644 --- a/examples/agents/adk/03_structured_output.py +++ b/examples/agents/adk/03_structured_output.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Agent with Structured Output — enforced JSON schema response. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install google-adk pydantic - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from typing import List @@ -74,7 +71,7 @@ class Recipe(BaseModel): # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.03_structured_output + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/04_sub_agents.py b/examples/agents/adk/04_sub_agents.py index 3d20635d4..533137809 100644 --- a/examples/agents/adk/04_sub_agents.py +++ b/examples/agents/adk/04_sub_agents.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Agent with Sub-Agents — multi-agent orchestration. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -148,7 +145,7 @@ def get_travel_advisory(country: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.adk.04_sub_agents + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/adk/05_generation_config.py b/examples/agents/adk/05_generation_config.py index 838c3a632..fe536cafe 100644 --- a/examples/agents/adk/05_generation_config.py +++ b/examples/agents/adk/05_generation_config.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Agent with Generation Config — temperature and output control. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -62,7 +59,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(factual_agent) # CLI alternative: - # agentspan deploy --package examples.adk.05_generation_config + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(factual_agent) diff --git a/examples/agents/adk/06_streaming.py b/examples/agents/adk/06_streaming.py index b5e65af97..d619b336a 100644 --- a/examples/agents/adk/06_streaming.py +++ b/examples/agents/adk/06_streaming.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Agent with Streaming — real-time event streaming. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -71,7 +68,7 @@ def search_documentation(query: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.06_streaming + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/07_output_key_state.py b/examples/agents/adk/07_output_key_state.py index f50a21ef7..e9951cf43 100644 --- a/examples/agents/adk/07_output_key_state.py +++ b/examples/agents/adk/07_output_key_state.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Agent with Output Key — state management via output_key. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -115,7 +112,7 @@ def generate_chart_description(metric: str, value: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.adk.07_output_key_state + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/adk/08_instruction_templating.py b/examples/agents/adk/08_instruction_templating.py index ea4b0e61e..6135c5816 100644 --- a/examples/agents/adk/08_instruction_templating.py +++ b/examples/agents/adk/08_instruction_templating.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Agent with Instruction Templating — dynamic {variable} injection. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -101,7 +98,7 @@ def search_tutorials(topic: str, level: str = "intermediate") -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.08_instruction_templating + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/09_multi_tool_agent.py b/examples/agents/adk/09_multi_tool_agent.py index 9bcafc443..971141750 100644 --- a/examples/agents/adk/09_multi_tool_agent.py +++ b/examples/agents/adk/09_multi_tool_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Agent with Multiple Specialized Tools — complex tool orchestration. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from typing import List @@ -157,7 +154,7 @@ def apply_coupon(subtotal: float, coupon_code: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.09_multi_tool_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/10_hierarchical_agents.py b/examples/agents/adk/10_hierarchical_agents.py index ace6c0f2b..9dea6e0de 100644 --- a/examples/agents/adk/10_hierarchical_agents.py +++ b/examples/agents/adk/10_hierarchical_agents.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Hierarchical Agents — multi-level agent delegation. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install google-adk - Conductor server with Google Gemini LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -180,7 +177,7 @@ def check_performance_metrics(service: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.adk.10_hierarchical_agents + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/adk/11_sequential_agent.py b/examples/agents/adk/11_sequential_agent.py index 8e738312c..78c7dea06 100644 --- a/examples/agents/adk/11_sequential_agent.py +++ b/examples/agents/adk/11_sequential_agent.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Sequential Agent Pipeline — SequentialAgent runs sub-agents in fixed order. Mirrors the pattern from Google ADK samples (story_teller, llm-auditor). @@ -63,7 +60,7 @@ def main(): # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.adk.11_sequential_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/adk/12_parallel_agent.py b/examples/agents/adk/12_parallel_agent.py index 75d14c227..100f7b027 100644 --- a/examples/agents/adk/12_parallel_agent.py +++ b/examples/agents/adk/12_parallel_agent.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Parallel Agent — ParallelAgent runs sub-agents concurrently. Mirrors the pattern from Google ADK samples (story_teller, parallel_task_decomposition). @@ -63,7 +60,7 @@ def main(): # 1. Deploy once during CI/CD: # runtime.deploy(parallel_analysis) # CLI alternative: - # agentspan deploy --package examples.adk.12_parallel_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(parallel_analysis) diff --git a/examples/agents/adk/13_loop_agent.py b/examples/agents/adk/13_loop_agent.py index bc4fa2307..cbf3e078e 100644 --- a/examples/agents/adk/13_loop_agent.py +++ b/examples/agents/adk/13_loop_agent.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Loop Agent — LoopAgent repeats sub-agents for iterative refinement. Mirrors the pattern from Google ADK samples (story_teller, image-scoring). @@ -62,7 +59,7 @@ def main(): # 1. Deploy once during CI/CD: # runtime.deploy(refinement_loop) # CLI alternative: - # agentspan deploy --package examples.adk.13_loop_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(refinement_loop) diff --git a/examples/agents/adk/14_callbacks.py b/examples/agents/adk/14_callbacks.py index 24b71e86e..e61b99b4f 100644 --- a/examples/agents/adk/14_callbacks.py +++ b/examples/agents/adk/14_callbacks.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Callbacks — before_tool_callback and after_tool_callback for tool interception. Mirrors the pattern from Google ADK samples (customer-service). @@ -79,7 +76,7 @@ def check_order_status(order_id: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.14_callbacks + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/15_global_instruction.py b/examples/agents/adk/15_global_instruction.py index 59e4a204e..e95d58188 100644 --- a/examples/agents/adk/15_global_instruction.py +++ b/examples/agents/adk/15_global_instruction.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Global Instruction — global_instruction for system-wide context. Mirrors the pattern from Google ADK samples (data-science, customer-service). @@ -81,7 +78,7 @@ def get_store_hours(location: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.15_global_instruction + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/16_customer_service.py b/examples/agents/adk/16_customer_service.py index 261783021..3296b6b56 100644 --- a/examples/agents/adk/16_customer_service.py +++ b/examples/agents/adk/16_customer_service.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Customer Service — Real-world multi-tool agent pattern from ADK samples. Mirrors the customer-service ADK sample. A single agent with multiple @@ -103,7 +100,7 @@ def update_account_plan(account_id: str, new_plan: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.16_customer_service + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/17_financial_advisor.py b/examples/agents/adk/17_financial_advisor.py index 4ad792d96..e85f0b56e 100644 --- a/examples/agents/adk/17_financial_advisor.py +++ b/examples/agents/adk/17_financial_advisor.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Financial Advisor — Multi-agent with specialized tool-using sub-agents. Mirrors the financial-advisor ADK sample. A coordinator agent delegates @@ -141,7 +138,7 @@ def estimate_tax_impact(gains: float, holding_period_months: int) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.adk.17_financial_advisor + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/adk/18_order_processing.py b/examples/agents/adk/18_order_processing.py index 0e03f1242..9fb7e9c21 100644 --- a/examples/agents/adk/18_order_processing.py +++ b/examples/agents/adk/18_order_processing.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Order Processing — End-to-end order management agent. Mirrors the order-processing ADK sample. A single agent handles the @@ -102,7 +99,7 @@ def place_order(item_skus: str, shipping_method: str = "standard", payment_metho # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.18_order_processing + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/19_supply_chain.py b/examples/agents/adk/19_supply_chain.py index 151398a65..bc95e215b 100644 --- a/examples/agents/adk/19_supply_chain.py +++ b/examples/agents/adk/19_supply_chain.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Supply Chain — Multi-agent supply chain management. Mirrors the supply-chain ADK sample. A coordinator delegates to @@ -136,7 +133,7 @@ def get_demand_forecast(sku: str, weeks_ahead: int = 4) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.adk.19_supply_chain + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/adk/20_blog_writer.py b/examples/agents/adk/20_blog_writer.py index 477f876c7..c6f703a91 100644 --- a/examples/agents/adk/20_blog_writer.py +++ b/examples/agents/adk/20_blog_writer.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Blog Writer — Sequential pipeline for content creation. Mirrors the blog-writer ADK sample. Sub-agents with output_key collaborate @@ -117,7 +114,7 @@ def check_seo_keywords(topic: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.adk.20_blog_writer + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/adk/21_agent_tool.py b/examples/agents/adk/21_agent_tool.py index 6207c9247..2e6df48c8 100644 --- a/examples/agents/adk/21_agent_tool.py +++ b/examples/agents/adk/21_agent_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK AgentTool — agent-as-tool invocation. Demonstrates: @@ -18,8 +15,8 @@ Requirements: - pip install google-adk - Conductor server with AgentTool support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -130,7 +127,7 @@ def compute(expression: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(manager) # CLI alternative: - # agentspan deploy --package examples.adk.21_agent_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(manager) diff --git a/examples/agents/adk/22_transfer_control.py b/examples/agents/adk/22_transfer_control.py index f0461c9df..7d6de8cdf 100644 --- a/examples/agents/adk/22_transfer_control.py +++ b/examples/agents/adk/22_transfer_control.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Transfer Control — restricted agent handoffs. Demonstrates: @@ -18,8 +15,8 @@ Requirements: - pip install google-adk - Conductor server with transfer control support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import LlmAgent @@ -85,7 +82,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.adk.22_transfer_control + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/adk/23_callbacks.py b/examples/agents/adk/23_callbacks.py index 54be7784d..94aac216c 100644 --- a/examples/agents/adk/23_callbacks.py +++ b/examples/agents/adk/23_callbacks.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Callbacks — lifecycle hooks on agent execution. Demonstrates: @@ -16,8 +13,8 @@ Requirements: - pip install google-adk - Conductor server with callback support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ import json @@ -97,7 +94,7 @@ def inspect_after_model(callback_position: str, agent_name: str, # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.23_callbacks + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/24_planner.py b/examples/agents/adk/24_planner.py index 9776fe633..5ab8e2537 100644 --- a/examples/agents/adk/24_planner.py +++ b/examples/agents/adk/24_planner.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK BuiltInPlanner — agent with planning step. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install google-adk - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import LlmAgent @@ -97,7 +94,7 @@ def write_section(title: str, content: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.24_planner + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/25_camel_security.py b/examples/agents/adk/25_camel_security.py index 166c01372..872331c92 100644 --- a/examples/agents/adk/25_camel_security.py +++ b/examples/agents/adk/25_camel_security.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """CaMeL-inspired Security Policy Agent — controlled data flow. Demonstrates: @@ -14,8 +11,8 @@ Requirements: - pip install google-adk - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent, SequentialAgent @@ -135,7 +132,7 @@ def redact_sensitive_fields(data: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.adk.25_camel_security + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/adk/26_safety_guardrails.py b/examples/agents/adk/26_safety_guardrails.py index 64b0eff23..e82c4d55f 100644 --- a/examples/agents/adk/26_safety_guardrails.py +++ b/examples/agents/adk/26_safety_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Safety Guardrails — global safety enforcement using LLM-as-judge. Demonstrates: @@ -14,8 +11,8 @@ Requirements: - pip install google-adk - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ import re @@ -124,7 +121,7 @@ def sanitize_response(text: str, pii_types: str = "") -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(safe_pipeline) # CLI alternative: - # agentspan deploy --package examples.adk.26_safety_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(safe_pipeline) diff --git a/examples/agents/adk/27_security_agent.py b/examples/agents/adk/27_security_agent.py index a8467da04..acf050c60 100644 --- a/examples/agents/adk/27_security_agent.py +++ b/examples/agents/adk/27_security_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """AI Security Agent — red-team testing with multi-agent evaluation. Demonstrates: @@ -16,8 +13,8 @@ Requirements: - pip install google-adk - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent, SequentialAgent @@ -140,7 +137,7 @@ def score_safety(response_text: str, attack_category: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(security_test) # CLI alternative: - # agentspan deploy --package examples.adk.27_security_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(security_test) diff --git a/examples/agents/adk/28_movie_pipeline.py b/examples/agents/adk/28_movie_pipeline.py index d3e51b68a..30c915402 100644 --- a/examples/agents/adk/28_movie_pipeline.py +++ b/examples/agents/adk/28_movie_pipeline.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Short Movie Pipeline — sequential content generation stages. Demonstrates: @@ -14,8 +11,8 @@ Requirements: - pip install google-adk - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent, SequentialAgent @@ -213,7 +210,7 @@ def assemble_production(title: str, total_scenes: int, # 1. Deploy once during CI/CD: # runtime.deploy(movie_pipeline) # CLI alternative: - # agentspan deploy --package examples.adk.28_movie_pipeline + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(movie_pipeline) diff --git a/examples/agents/adk/29_include_contents.py b/examples/agents/adk/29_include_contents.py index e9341560e..57e077e5a 100644 --- a/examples/agents/adk/29_include_contents.py +++ b/examples/agents/adk/29_include_contents.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Include Contents — control context passed to sub-agents. When ``include_contents="none"``, a sub-agent starts fresh without @@ -9,8 +6,8 @@ Requirements: - pip install google-adk - Conductor server with include_contents support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -63,7 +60,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(coordinator) # CLI alternative: - # agentspan deploy --package examples.adk.29_include_contents + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(coordinator) diff --git a/examples/agents/adk/30_thinking_config.py b/examples/agents/adk/30_thinking_config.py index c6ed84a1c..56006f694 100644 --- a/examples/agents/adk/30_thinking_config.py +++ b/examples/agents/adk/30_thinking_config.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Thinking Config — extended reasoning for complex tasks. Uses ADK's ThinkingConfig to enable extended thinking mode, @@ -9,8 +6,8 @@ Requirements: - pip install google-adk - Conductor server with thinking config support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -65,7 +62,7 @@ def calculate(expression: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.30_thinking_config + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/31_shared_state.py b/examples/agents/adk/31_shared_state.py index 647cceeff..0a66c7ba1 100644 --- a/examples/agents/adk/31_shared_state.py +++ b/examples/agents/adk/31_shared_state.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Shared State — tools sharing state via ToolContext. Tools can read and write ``context.state``, a dictionary that persists @@ -9,8 +6,8 @@ Requirements: - pip install google-adk - Conductor server with state support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent @@ -90,7 +87,7 @@ def clear_list(tool_context: ToolContext) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.adk.31_shared_state + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/adk/32_nested_strategies.py b/examples/agents/adk/32_nested_strategies.py index 481335352..345b1babf 100644 --- a/examples/agents/adk/32_nested_strategies.py +++ b/examples/agents/adk/32_nested_strategies.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK Nested Strategies — ParallelAgent inside SequentialAgent. Demonstrates composing agent strategies: parallel research runs @@ -9,8 +6,8 @@ Requirements: - pip install google-adk - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash as environment variable """ from google.adk.agents import Agent, ParallelAgent, SequentialAgent @@ -76,7 +73,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.adk.32_nested_strategies + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(pipeline) diff --git a/examples/agents/adk/33_software_bug_assistant.py b/examples/agents/adk/33_software_bug_assistant.py index 0b8bd07f1..2782cf18f 100644 --- a/examples/agents/adk/33_software_bug_assistant.py +++ b/examples/agents/adk/33_software_bug_assistant.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Software Bug Assistant — agent_tool + mcp_tool for bug triage. Mirrors the pattern from google/adk-samples/software-bug-assistant. @@ -21,8 +18,8 @@ Requirements: - Conductor server with AgentTool + MCP support - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash in .env or environment - GH_TOKEN in .env or environment """ @@ -281,7 +278,7 @@ def search_web(query: str) -> dict: # 1. Deploy once during CI/CD: # runtime.deploy(software_assistant) # CLI alternative: - # agentspan deploy --package examples.adk.33_software_bug_assistant + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(software_assistant) diff --git a/examples/agents/adk/34_ml_engineering.py b/examples/agents/adk/34_ml_engineering.py index 6ac29765d..b3033e525 100644 --- a/examples/agents/adk/34_ml_engineering.py +++ b/examples/agents/adk/34_ml_engineering.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK ML Engineering Pipeline — multi-agent ML workflow. Mirrors the pattern from google/adk-samples/machine-learning-engineering (MLE-STAR). @@ -28,8 +25,8 @@ Requirements: - pip install google-adk - Conductor server - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash in .env or environment """ from google.adk.agents import Agent, LoopAgent, ParallelAgent, SequentialAgent @@ -214,7 +211,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(ml_pipeline) # CLI alternative: - # agentspan deploy --package examples.adk.34_ml_engineering + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(ml_pipeline) diff --git a/examples/agents/adk/35_rag_agent.py b/examples/agents/adk/35_rag_agent.py index 5735849cc..250224bf1 100644 --- a/examples/agents/adk/35_rag_agent.py +++ b/examples/agents/adk/35_rag_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Google ADK RAG Agent — vector search + document indexing. Mirrors the pattern from google/adk-samples/RAG but uses Conductor's native @@ -27,8 +24,8 @@ - pip install google-adk - Conductor server with RAG system tasks enabled (--spring.profiles.active=rag) - A configured vector database (e.g., pgvector) - - AGENTSPAN_SERVER_URL=http://localhost:8080/api in .env or environment - - AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash in .env or environment + - CONDUCTOR_SERVER_URL=http://localhost:8080/api in .env or environment + - CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash in .env or environment """ from conductor.ai.agents import Agent, AgentRuntime, search_tool, index_tool @@ -208,7 +205,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(rag_agent) # CLI alternative: - # agentspan deploy --package examples.adk.35_rag_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(rag_agent) diff --git a/examples/agents/adk/README.md b/examples/agents/adk/README.md index d600e9577..674d4fc4c 100644 --- a/examples/agents/adk/README.md +++ b/examples/agents/adk/README.md @@ -1,81 +1,9 @@ -# Google ADK Examples +# Google ADK Conductor-agent examples -These examples demonstrate running agents written with [Google's Agent Development Kit (ADK)](https://github.com/google/adk-python) (`google-adk`) on the Agentspan runtime. +These examples run standard Google ADK agents through the durable Conductor agent +runtime. Install `conductor-python[adk]`, configure a server-side Gemini/provider +integration, and set `CONDUCTOR_SERVER_URL` plus `CONDUCTOR_AGENT_LLM_MODEL`. -The agents are defined using standard ADK classes — Agentspan auto-detects the framework, serializes the agent generically, and the server normalizes the config into an agent execution. **Zero translation code in the SDK.** - -## Prerequisites - -```bash -uv pip install google-adk conductor-agent-sdk -``` - -| Package | Required | Notes | -|---------|----------|-------| -| `google-adk` | Yes | `Agent`, `SequentialAgent`, `ParallelAgent`, `LoopAgent`, planners | -| `pydantic` | Some examples | Used for structured output (03) | - -Export environment variables: - -```bash -export AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini -export AGENTSPAN_SERVER_URL=http://localhost:8080/api -export GOOGLE_GEMINI_API_KEY=your-key -``` - -## Examples - -| # | File | Feature | Description | -|---|------|---------|-------------| -| 01 | [01_basic_agent.py](01_basic_agent.py) | **Basic Agent** | Simplest agent — single LLM, no tools. Shows auto-detection and server normalization. | -| 02 | [02_function_tools.py](02_function_tools.py) | **Function Tools** | Multiple Python functions as tools with typed params and docstrings. ADK auto-converts them. | -| 03 | [03_structured_output.py](03_structured_output.py) | **Structured Output** | Pydantic `output_schema` for enforced JSON responses. Combined with `generate_content_config`. | -| 04 | [04_sub_agents.py](04_sub_agents.py) | **Sub-Agents** | Multi-agent orchestration with coordinator → specialist routing via `sub_agents`. | -| 05 | [05_generation_config.py](05_generation_config.py) | **Generation Config** | `generate_content_config` for temperature and output token control. Creative vs. factual agents. | -| 06 | [06_streaming.py](06_streaming.py) | **Streaming** | Default `runtime.run()` flow with a commented `runtime.stream()` alternative for SSE events. | -| 07 | [07_output_key_state.py](07_output_key_state.py) | **Output Key & State** | `output_key` for storing agent results in session state. Multi-agent data passing. | -| 08 | [08_instruction_templating.py](08_instruction_templating.py) | **Instruction Templating** | ADK's `{variable}` syntax in instructions for dynamic context injection from state. | -| 09 | [09_multi_tool_agent.py](09_multi_tool_agent.py) | **Multi-Tool Agent** | Complex tool orchestration with 4 tools (search, inventory, shipping, coupons). Best-practice dict returns. | -| 10 | [10_hierarchical_agents.py](10_hierarchical_agents.py) | **Hierarchical Agents** | Multi-level delegation: coordinator → team leads → specialists. Deep sub_agents nesting. | - -## Feature Coverage - -| Google ADK Feature | Example(s) | -|---|---| -| `Agent` class | All | -| Function tools (auto-converted) | 02, 04, 06, 07, 08, 09, 10 | -| `sub_agents` (multi-agent) | 04, 07, 10 | -| `output_schema` (structured output) | 03 | -| `generate_content_config` (temperature, tokens) | 03, 05 | -| `output_key` (state management) | 07 | -| `instruction` templating (`{var}`) | 08 | -| `description` (for agent routing) | 04, 10 | -| Streaming (`runtime.stream()`, commented alternative) | 06 | -| Multi-tool orchestration | 09 | -| Hierarchical sub-agents (3 levels) | 10 | - -## How It Works - -``` -Google ADK Agent object - │ - ▼ (auto-detected by type(agent).__module__.startswith("google.adk")) -Generic serializer → JSON dict + callable extraction - │ - ▼ POST /api/agent/start { framework: "google_adk", rawConfig: {...} } -Server GoogleADKNormalizer → AgentConfig → Conductor WorkflowDef - │ - ▼ -Agentspan runtime executes the agent -``` - -## Key ADK Differences from OpenAI - -| Concept | Google ADK | OpenAI Agents SDK | -|---|---|---| -| Instructions | `instruction` (singular) | `instructions` (plural) | -| Multi-agent | `sub_agents` | `handoffs` | -| Model config | `generate_content_config` dict | `ModelSettings` class | -| Structured output | `output_schema` | `output_type` | -| Tool definition | Plain Python functions | `@function_tool` decorator | -| State management | `output_key` + `{var}` templating | Context/Sessions | +Run examples from `examples/agents` so shared settings resolve correctly. See the +[Google ADK guide](../../../docs/agents/frameworks/google-adk.md) for the supported +bridge contract. diff --git a/examples/agents/adk/run_all.py b/examples/agents/adk/run_all.py index d56096949..5d30ad998 100644 --- a/examples/agents/adk/run_all.py +++ b/examples/agents/adk/run_all.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Run all Google ADK agent examples and verify correctness. Usage: diff --git a/examples/agents/blog-and-video-examples/handoff/03_issue_triage_github_discord.py b/examples/agents/blog-and-video-examples/handoff/03_issue_triage_github_discord.py index 5eab5309e..f5391ef0c 100644 --- a/examples/agents/blog-and-video-examples/handoff/03_issue_triage_github_discord.py +++ b/examples/agents/blog-and-video-examples/handoff/03_issue_triage_github_discord.py @@ -20,10 +20,10 @@ 8. Specialist calls post_to_discord() — notifies the right channel Setup: - pip install agentspan requests - agentspan server start + pip install Conductor requests + Conductor server start - # Store credentials in the AgentSpan UI (localhost:8080 → Credentials): + # Store credentials in the Conductor UI (localhost:8080 → Credentials): # GITHUB_TOKEN = GitHub personal access token (needs repo scope) # DISCORD_TOKEN = Discord bot token diff --git a/examples/agents/blog-and-video-examples/handoff/03_issue_triage_handoff.py b/examples/agents/blog-and-video-examples/handoff/03_issue_triage_handoff.py index a3b576c98..a2ae234cd 100644 --- a/examples/agents/blog-and-video-examples/handoff/03_issue_triage_handoff.py +++ b/examples/agents/blog-and-video-examples/handoff/03_issue_triage_handoff.py @@ -11,8 +11,8 @@ the routing at runtime — not a fixed pipeline, not keyword matching. Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python 03_issue_triage_handoff.py """ diff --git a/examples/agents/blog-and-video-examples/manual/07_editorial_manual.py b/examples/agents/blog-and-video-examples/manual/07_editorial_manual.py index 7e0c9ea3e..fcc72e80e 100644 --- a/examples/agents/blog-and-video-examples/manual/07_editorial_manual.py +++ b/examples/agents/blog-and-video-examples/manual/07_editorial_manual.py @@ -5,8 +5,8 @@ based on what the draft needs at each stage. Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python 08_editorial_manual.py """ diff --git a/examples/agents/blog-and-video-examples/parallel_execution/02_code_review_parallel.py b/examples/agents/blog-and-video-examples/parallel_execution/02_code_review_parallel.py index 2b4bf49bf..c1d3774e4 100644 --- a/examples/agents/blog-and-video-examples/parallel_execution/02_code_review_parallel.py +++ b/examples/agents/blog-and-video-examples/parallel_execution/02_code_review_parallel.py @@ -15,8 +15,8 @@ - sub_results for per-agent outputs Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python 02_code_review_parallel.py """ diff --git a/examples/agents/blog-and-video-examples/parallel_execution/02_code_review_parallel_github.py b/examples/agents/blog-and-video-examples/parallel_execution/02_code_review_parallel_github.py index 88df8c109..53528011c 100644 --- a/examples/agents/blog-and-video-examples/parallel_execution/02_code_review_parallel_github.py +++ b/examples/agents/blog-and-video-examples/parallel_execution/02_code_review_parallel_github.py @@ -10,10 +10,10 @@ PR diff from GitHub and posts the review as a PR comment. Setup: - pip install agentspan requests - agentspan server start + pip install Conductor requests + Conductor server start - # Store credentials in the AgentSpan UI (localhost:8080 → Credentials): + # Store credentials in the Conductor UI (localhost:8080 → Credentials): # GITHUB_TOKEN = your GitHub personal access token (needs repo scope) python 02_code_review_parallel_github.py @@ -157,7 +157,7 @@ def post_pr_review(repo: str, pr_number: int, body: str) -> dict: # ── Run ─────────────────────────────────────────────────────────── if __name__ == "__main__": - REPO = "deeptireddy-lab/agentspan-metrics" + REPO = "deeptireddy-lab/Conductor-metrics" PR_NUMBER = 1 with AgentRuntime() as runtime: diff --git a/examples/agents/blog-and-video-examples/random/06_brainstorm_random.py b/examples/agents/blog-and-video-examples/random/06_brainstorm_random.py index 25b720b3f..6b48a974e 100644 --- a/examples/agents/blog-and-video-examples/random/06_brainstorm_random.py +++ b/examples/agents/blog-and-video-examples/random/06_brainstorm_random.py @@ -5,8 +5,8 @@ which perspective comes next. Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python 07_brainstorm_random.py """ diff --git a/examples/agents/blog-and-video-examples/random/07_brainstorm_random.py b/examples/agents/blog-and-video-examples/random/07_brainstorm_random.py index 25b720b3f..6b48a974e 100644 --- a/examples/agents/blog-and-video-examples/random/07_brainstorm_random.py +++ b/examples/agents/blog-and-video-examples/random/07_brainstorm_random.py @@ -5,8 +5,8 @@ which perspective comes next. Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python 07_brainstorm_random.py """ diff --git a/examples/agents/blog-and-video-examples/random/07_brainstorm_random_blog.md b/examples/agents/blog-and-video-examples/random/07_brainstorm_random_blog.md index 468e27839..ab7c02991 100644 --- a/examples/agents/blog-and-video-examples/random/07_brainstorm_random_blog.md +++ b/examples/agents/blog-and-video-examples/random/07_brainstorm_random_blog.md @@ -2,7 +2,7 @@ *By Deepti Reddy | May 2026* -*This is Part 6 of an 8-part series covering every multi-agent strategy in Agentspan. Today: the random strategy — a random agent is selected each turn. No rotation, no decision — pure randomness.* +*This is Part 6 of an 8-part series covering Conductor-agent multi-agent strategies. Today: the random strategy — a random agent is selected each turn. No rotation, no decision — pure randomness.* --- @@ -12,12 +12,12 @@ But what if predictability is the problem? In brainstorming, you do not want a f That is the random strategy. Each turn, a random agent is selected. No pattern. No schedule. The same agent might go twice in a row, or not at all for three turns. The randomness creates variety in perspective that a fixed rotation cannot. -## What is Agentspan +## What are Conductor agents -Agentspan is an orchestration layer for building, bringing, and observing AI agents as durable workflows. +Conductor agents are an orchestration layer for building, bringing, and observing AI agents as durable workflows. -- **Build**: define agents with the Agentspan SDK using Agent, @tool, and 8 multi-agent strategies. Compiles to server-side workflows that survive crashes. -- **Bring**: already using an agent framework such as LangGraph, OpenAI Agents SDK, or Google ADK? Pass your agents directly to run(). Agentspan adds durability and orchestration on top. +- **Build**: define agents with the Conductor Python SDK using Agent, @tool, and multi-agent strategies. Compiles to server-side workflows that survive crashes. +- **Bring**: already using an agent framework such as LangGraph, OpenAI Agents SDK, or Google ADK? Pass your agents directly to run(). Conductor adds durability and orchestration on top. - **Observe**: every execution is inspectable in the dashboard. See agent flows, inputs/outputs, tool calls, and token usage. Debug failures, replay runs. ## Setup @@ -26,10 +26,10 @@ Two commands: ```bash pip install conductor-agent-sdk -agentspan server start +conductor server start ``` -This gives you a local Agentspan server with a visual dashboard at localhost:8080. +This gives you a local Conductor server with a visual dashboard at localhost:8080. ## What we are building @@ -221,13 +221,13 @@ Random generates ideas. Round robin reviews them. The summarizer produces the fi ```bash pip install conductor-agent-sdk -agentspan server start +conductor server start python 07_brainstorm_random.py ``` -- **GitHub**: [github.com/agentspan-ai/agentspan](https://github.com/agentspan-ai/agentspan) -- **Blog examples**: [github.com/agentspan-ai/agentspan/tree/main/sdk/python/examples/blog_and_videos/random](https://github.com/agentspan-ai/agentspan/tree/main/sdk/python/examples/blog_and_videos/random) -- **Docs**: [agentspan.ai/docs](https://agentspan.ai/docs) +- **GitHub**: [conductor-oss/conductor](https://github.com/conductor-oss/conductor) +- **Blog examples**: [Python SDK examples](https://github.com/conductoross/sdk/tree/main/python-sdk/examples/agents) +- **Docs**: [Python SDK documentation](../../../README.md) - **Discord**: [https://discord.com/invite/ajcA66JcKq](https://discord.com/invite/ajcA66JcKq) ## What's next diff --git a/examples/agents/blog-and-video-examples/round_robin/06_code_review_debate.py b/examples/agents/blog-and-video-examples/round_robin/06_code_review_debate.py index 369408d7f..ada606ea1 100644 --- a/examples/agents/blog-and-video-examples/round_robin/06_code_review_debate.py +++ b/examples/agents/blog-and-video-examples/round_robin/06_code_review_debate.py @@ -5,8 +5,8 @@ After the debate, a summarizer produces the final verdict. Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python 06_code_review_debate.py """ diff --git a/examples/agents/blog-and-video-examples/sequential_pipeline/02_support_ticket_pipeline.py b/examples/agents/blog-and-video-examples/sequential_pipeline/02_support_ticket_pipeline.py index af5624f2c..8c80cf634 100644 --- a/examples/agents/blog-and-video-examples/sequential_pipeline/02_support_ticket_pipeline.py +++ b/examples/agents/blog-and-video-examples/sequential_pipeline/02_support_ticket_pipeline.py @@ -15,8 +15,8 @@ - Three specialist agents chained together Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python 02_support_ticket_pipeline.py """ diff --git a/examples/agents/blog-and-video-examples/sequential_pipeline/02_support_ticket_zendesk.py b/examples/agents/blog-and-video-examples/sequential_pipeline/02_support_ticket_zendesk.py index 5e80a98a7..afda08f85 100644 --- a/examples/agents/blog-and-video-examples/sequential_pipeline/02_support_ticket_zendesk.py +++ b/examples/agents/blog-and-video-examples/sequential_pipeline/02_support_ticket_zendesk.py @@ -10,10 +10,10 @@ from Zendesk instead of using hardcoded input. Setup: - pip install agentspan requests - agentspan server start + pip install Conductor requests + Conductor server start - # Store credentials in the AgentSpan UI (localhost:8080 → Credentials): + # Store credentials in the Conductor UI (localhost:8080 → Credentials): # ZENDESK_API = your Zendesk API token # ZENDESK_EMAIL = your Zendesk email (e.g. you@company.com) @@ -26,7 +26,7 @@ # ── Zendesk Tools ──────────────────────────────────────────────── -# Credentials are injected into os.environ by the AgentSpan server +# Credentials are injected into os.environ by the Conductor server # at execution time. No secrets in code. ZENDESK_SUBDOMAIN = "orkeshelp" diff --git a/examples/agents/blog-and-video-examples/swarm/04_support_swarm.py b/examples/agents/blog-and-video-examples/swarm/04_support_swarm.py index 586ae9b64..79db5b436 100644 --- a/examples/agents/blog-and-video-examples/swarm/04_support_swarm.py +++ b/examples/agents/blog-and-video-examples/swarm/04_support_swarm.py @@ -5,8 +5,8 @@ back to the front-line. Peer-to-peer, not top-down. Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python 05_support_swarm.py """ diff --git a/examples/agents/blog_and_videos/email-subscription-agent/README.md b/examples/agents/blog_and_videos/email-subscription-agent/README.md index b197c35fa..c35ebe71a 100644 --- a/examples/agents/blog_and_videos/email-subscription-agent/README.md +++ b/examples/agents/blog_and_videos/email-subscription-agent/README.md @@ -2,7 +2,7 @@ An AI agent that scans your email inbox for recurring charges, flags unused or duplicate subscriptions, and tells you exactly what to cancel and where — with a running total of how much you'd save. -Built with [Agentspan](https://agentspan.ai/). +Built with [Conductor](https://github.com/conductor-oss/conductor). --- @@ -28,7 +28,7 @@ By default it runs on sample inbox data so you can try it immediately with no se ## Setup -**1. Install Agentspan** +**1. Install the Conductor Python SDK** ```bash pip install conductor-agent-sdk @@ -40,12 +40,12 @@ pip install conductor-agent-sdk export ANTHROPIC_API_KEY=your_key_here ``` -**3. Start the Agentspan server** +**3. Start the Conductor server** -Agentspan runs on top of Conductor, which needs a local server process running in the background. +Conductor needs a local server process running in the background. ```bash -agentspan server start +conductor server start ``` **4. Run the agent** diff --git a/examples/agents/blog_and_videos/router/04_router_triage.py b/examples/agents/blog_and_videos/router/04_router_triage.py index ae6b3b71b..cf17fbc8b 100644 --- a/examples/agents/blog_and_videos/router/04_router_triage.py +++ b/examples/agents/blog_and_videos/router/04_router_triage.py @@ -5,8 +5,8 @@ brains, each doing what it is good at. Setup: - pip install agentspan - agentspan server start + pip install Conductor + Conductor server start python split-the-brain.py """ diff --git a/examples/agents/claude_agent_sdk/01_basic_agent.py b/examples/agents/claude_agent_sdk/01_basic_agent.py index b7516a193..ac017093a 100644 --- a/examples/agents/claude_agent_sdk/01_basic_agent.py +++ b/examples/agents/claude_agent_sdk/01_basic_agent.py @@ -6,7 +6,7 @@ export ANTHROPIC_API_KEY=sk-... Usage: - # Start the agentspan server first, then: + # Start the Conductor server first, then: uv run python examples/claude_agent_sdk/01_basic_agent.py """ @@ -38,7 +38,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(reviewer) # CLI alternative: - # agentspan deploy --package examples.claude_agent_sdk.01_basic_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(reviewer) diff --git a/examples/agents/claude_agent_sdk/02_claude_code_config.py b/examples/agents/claude_agent_sdk/02_claude_code_config.py index c52f1ab21..d5fcd3475 100644 --- a/examples/agents/claude_agent_sdk/02_claude_code_config.py +++ b/examples/agents/claude_agent_sdk/02_claude_code_config.py @@ -36,7 +36,7 @@ def main(): # 1. Deploy once during CI/CD: # runtime.deploy(reviewer) # CLI alternative: - # agentspan deploy --package examples.claude_agent_sdk.02_claude_code_config + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(reviewer) diff --git a/examples/agents/claude_agent_sdk/05_build_and_review.py b/examples/agents/claude_agent_sdk/05_build_and_review.py index c493932d5..5cf58b2be 100644 --- a/examples/agents/claude_agent_sdk/05_build_and_review.py +++ b/examples/agents/claude_agent_sdk/05_build_and_review.py @@ -99,7 +99,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(build_review_swarm) # CLI alternative: - # agentspan deploy --package examples.claude_agent_sdk.05_build_and_review + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(build_review_swarm) diff --git a/examples/agents/claude_agent_sdk/06_github_issue_swarm.py b/examples/agents/claude_agent_sdk/06_github_issue_swarm.py index 6e42c7ee8..0c0600f3e 100644 --- a/examples/agents/claude_agent_sdk/06_github_issue_swarm.py +++ b/examples/agents/claude_agent_sdk/06_github_issue_swarm.py @@ -577,7 +577,7 @@ def shell(command: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(github_coding_swarm) # CLI alternative: - # agentspan deploy --package examples.claude_agent_sdk.06_github_issue_swarm + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(github_coding_swarm) diff --git a/examples/agents/dump_agent_configs.py b/examples/agents/dump_agent_configs.py index df1eaddb1..4a5684f7d 100644 --- a/examples/agents/dump_agent_configs.py +++ b/examples/agents/dump_agent_configs.py @@ -16,8 +16,8 @@ # Ensure the examples directory is on the path for settings import sys.path.insert(0, os.path.dirname(__file__)) # Force a consistent model name so both SDKs produce identical values -os.environ["AGENTSPAN_LLM_MODEL"] = "openai/gpt-4o-mini" -os.environ["AGENTSPAN_SECONDARY_LLM_MODEL"] = "openai/gpt-4o" +os.environ["CONDUCTOR_AGENT_LLM_MODEL"] = "openai/gpt-4o-mini" +os.environ["CONDUCTOR_AGENT_SECONDARY_LLM_MODEL"] = "openai/gpt-4o" from conductor.ai.agents.config_serializer import AgentConfigSerializer diff --git a/examples/agents/hello_world_agent_schedule.py b/examples/agents/hello_world_agent_schedule.py index acc82ae83..3eeacb393 100644 --- a/examples/agents/hello_world_agent_schedule.py +++ b/examples/agents/hello_world_agent_schedule.py @@ -1,11 +1,11 @@ """Real-agent scheduling demo. -A genuine ``Agent(...)`` — LLM-backed — deployed via the agentspan SDK and -attached to a cron schedule in one call. Watches the agentspan-runtime fire +A genuine ``Agent(...)`` — LLM-backed — deployed via the Conductor SDK and +attached to a cron schedule in one call. Watches the Conductor-runtime fire the agent on a cadence and shows execution history with the LLM output. Requires: - - agentspan-runtime running on port 8080 with the scheduler module + - Conductor-runtime running on port 8080 with the scheduler module (see docs/design/plans/2026-05-27-agent-scheduling.md task 6) - An OPENAI_API_KEY (or change the model) @@ -28,10 +28,10 @@ SERVER = ( os.environ.get("CONDUCTOR_SERVER_URL") - or os.environ.get("AGENTSPAN_SERVER_URL") + or os.environ.get("CONDUCTOR_SERVER_URL") or "http://localhost:8080/api" ) -MODEL = os.environ.get("AGENTSPAN_MODEL", "anthropic/claude-sonnet-4-6") +MODEL = os.environ.get("CONDUCTOR_AGENT_MODEL", "anthropic/claude-sonnet-4-6") def fetch_executions(agent_name: str, limit: int = 20) -> list[dict]: diff --git a/examples/agents/hello_world_every_second.py b/examples/agents/hello_world_every_second.py index 6f0726369..8fe2fb743 100644 --- a/examples/agents/hello_world_every_second.py +++ b/examples/agents/hello_world_every_second.py @@ -22,7 +22,7 @@ from conductor.ai.agents.schedule import Schedule SERVER = "http://localhost:8080/api" -MODEL = os.environ.get("AGENTSPAN_MODEL", "anthropic/claude-sonnet-4-6") +MODEL = os.environ.get("CONDUCTOR_AGENT_MODEL", "anthropic/claude-sonnet-4-6") def fetch_executions(agent_name: str, limit: int = 30) -> list[dict]: diff --git a/examples/agents/hello_world_schedule.py b/examples/agents/hello_world_schedule.py index b99472942..4fdcbbd01 100644 --- a/examples/agents/hello_world_schedule.py +++ b/examples/agents/hello_world_schedule.py @@ -1,6 +1,6 @@ """Hello-world scheduling demo. -Schedules a "Hello, world!" workflow every 2 seconds via the agentspan SDK, +Schedules a "Hello, world!" workflow every 2 seconds via the Conductor SDK, waits, then prints the execution history. The "agent" here is a Conductor workflow with a single INLINE task that @@ -8,7 +8,7 @@ instead of a real LLM Agent keeps the demo deterministic and free — the scheduling pipeline is identical either way. -NOTE: Targets OSS Conductor at port 8089. The agentspan-runtime on port +NOTE: Targets OSS Conductor at port 8089. The Conductor-runtime on port 8080 doesn't (yet) include the scheduler module — see ``docs/design/plans/2026-05-27-agent-scheduling.md`` open dependency #1. @@ -31,7 +31,7 @@ CONDUCTOR_API = ( os.environ.get("CONDUCTOR_SERVER_URL") - or os.environ.get("AGENTSPAN_SERVER_URL") + or os.environ.get("CONDUCTOR_SERVER_URL") or "http://localhost:8080/api" ) @@ -42,7 +42,7 @@ def register_hello_world_workflow(name: str) -> None: "name": name, "version": 1, "description": "Hello-world demo for agent scheduling", - "ownerEmail": "demo@agentspan.test", + "ownerEmail": "demo@Conductor.test", "schemaVersion": 2, "timeoutSeconds": 30, "timeoutPolicy": "TIME_OUT_WF", diff --git a/examples/agents/kitchen_sink.py b/examples/agents/kitchen_sink.py index 56248be65..a08ee4bb9 100644 --- a/examples/agents/kitchen_sink.py +++ b/examples/agents/kitchen_sink.py @@ -1,9 +1,6 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Kitchen Sink — Content Publishing Platform. -A single mega-workflow that exercises every Agentspan SDK feature (89 features). +A single mega-workflow that exercises every Conductor SDK feature (89 features). See design/sdk-design/kitchen-sink.md for the full scenario specification. Demonstrates: @@ -28,13 +25,13 @@ # Or start with auth (requires storing the secret as a credential): mcp-testkit --transport http --auth <secret> - # Store credentials via CLI or Agentspan UI: - agentspan credentials set MCP_AUTH_TOKEN <secret> - agentspan credentials set SEARCH_API_KEY <key> + # Store credentials via CLI or Conductor UI: + the Conductor server credential store + the Conductor server credential store Requirements: - Conductor server with LLM support - - AGENTSPAN_SERVER_URL, AGENTSPAN_LLM_MODEL env vars + - CONDUCTOR_SERVER_URL, CONDUCTOR_AGENT_LLM_MODEL env vars - mcp-testkit running on http://localhost:3001 (for MCP/HTTP tools) - For full execution: Docker, credential store configured """ @@ -776,7 +773,7 @@ def should_handoff_to_publisher(messages: list, **kwargs) -> bool: # 1. Deploy once during CI/CD: # runtime.deploy(full_pipeline) # CLI alternative: - # agentspan deploy --package examples.kitchen_sink + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(full_pipeline) diff --git a/examples/agents/kitchen_sink_helpers.py b/examples/agents/kitchen_sink_helpers.py index 9b26e322a..62b6ae7a5 100644 --- a/examples/agents/kitchen_sink_helpers.py +++ b/examples/agents/kitchen_sink_helpers.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Kitchen Sink helpers — mock services, data fixtures, external worker stubs. These simulate external dependencies so the kitchen sink can run standalone diff --git a/examples/agents/langgraph/01_hello_world.py b/examples/agents/langgraph/01_hello_world.py index 9602cf5a2..c8105df8c 100644 --- a/examples/agents/langgraph/01_hello_world.py +++ b/examples/agents/langgraph/01_hello_world.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Hello World — simplest LangGraph agent with no tools. Demonstrates: @@ -9,7 +6,7 @@ - Printing the result Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -19,7 +16,7 @@ llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) -# create_agent with no tools — pure LLM chat, detected as langgraph by Agentspan +# create_agent with no tools — pure LLM chat, detected as langgraph by Conductor graph = create_agent(llm, tools=[], name="hello_world_agent") if __name__ == "__main__": @@ -32,7 +29,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.01_hello_world + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/02_react_with_tools.py b/examples/agents/langgraph/02_react_with_tools.py index 91b696fb9..e73ffc70e 100644 --- a/examples/agents/langgraph/02_react_with_tools.py +++ b/examples/agents/langgraph/02_react_with_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """ReAct Agent with Tools — create_agent with practical tools. Demonstrates: @@ -9,7 +6,7 @@ - Calculator, string operations, and date utilities Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -71,7 +68,7 @@ def get_today() -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.02_react_with_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/03_memory.py b/examples/agents/langgraph/03_memory.py index 5e4514035..351410874 100644 --- a/examples/agents/langgraph/03_memory.py +++ b/examples/agents/langgraph/03_memory.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Memory with MemorySaver — multi-turn conversation via checkpointer. Demonstrates: @@ -9,7 +6,7 @@ - How the agent remembers context from earlier messages Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -61,7 +58,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.03_memory + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/04_simple_stategraph.py b/examples/agents/langgraph/04_simple_stategraph.py index 19dc00d53..66a2664da 100644 --- a/examples/agents/langgraph/04_simple_stategraph.py +++ b/examples/agents/langgraph/04_simple_stategraph.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Simple StateGraph — custom query → process → answer pipeline. Demonstrates: @@ -10,7 +7,7 @@ - Compiling and naming the graph Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -79,7 +76,7 @@ def generate_answer(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.04_simple_stategraph + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/05_tool_node.py b/examples/agents/langgraph/05_tool_node.py index e323f1a64..08fd5da1f 100644 --- a/examples/agents/langgraph/05_tool_node.py +++ b/examples/agents/langgraph/05_tool_node.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """ToolNode — StateGraph with ToolNode + tools_condition for ReAct loop. Demonstrates: @@ -10,7 +7,7 @@ - Annotated list reducer for message accumulation Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -98,7 +95,7 @@ def call_model(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.05_tool_node + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/06_conditional_routing.py b/examples/agents/langgraph/06_conditional_routing.py index b092e9cbc..8d9690f56 100644 --- a/examples/agents/langgraph/06_conditional_routing.py +++ b/examples/agents/langgraph/06_conditional_routing.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Conditional Routing — StateGraph with add_conditional_edges. Demonstrates: @@ -9,7 +6,7 @@ - Multiple terminal nodes converging to END Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -104,7 +101,7 @@ def handle_neutral(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.06_conditional_routing + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/07_system_prompt.py b/examples/agents/langgraph/07_system_prompt.py index fee5f5e0d..90a994ac7 100644 --- a/examples/agents/langgraph/07_system_prompt.py +++ b/examples/agents/langgraph/07_system_prompt.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """System Prompt — create_agent with a detailed persona via system_prompt. Demonstrates: @@ -9,7 +6,7 @@ - How the system prompt shapes all LLM responses Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -54,7 +51,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.07_system_prompt + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/08_structured_output.py b/examples/agents/langgraph/08_structured_output.py index 8644e72d7..86d064d92 100644 --- a/examples/agents/langgraph/08_structured_output.py +++ b/examples/agents/langgraph/08_structured_output.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Structured Output — create_agent with response_format for Pydantic output. Demonstrates: @@ -9,7 +6,7 @@ - Accessing fields of the structured response Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -55,7 +52,7 @@ class MovieReview(BaseModel): # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.08_structured_output + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/09_math_agent.py b/examples/agents/langgraph/09_math_agent.py index 4e61c82b5..9ef6430c7 100644 --- a/examples/agents/langgraph/09_math_agent.py +++ b/examples/agents/langgraph/09_math_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Math Agent — create_agent with comprehensive arithmetic and math tools. Demonstrates: @@ -9,7 +6,7 @@ - Chaining multiple tool calls to solve multi-step problems Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -92,7 +89,7 @@ def factorial(n: int) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.09_math_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/10_research_agent.py b/examples/agents/langgraph/10_research_agent.py index f54c7c908..fa2f54f6b 100644 --- a/examples/agents/langgraph/10_research_agent.py +++ b/examples/agents/langgraph/10_research_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Research Agent — create_agent with search, summarize, and cite_source tools. Demonstrates: @@ -9,7 +6,7 @@ - Building a multi-step research workflow via tool chaining Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -109,7 +106,7 @@ def cite_source(claim: str, source_type: str = "academic") -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.10_research_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/11_customer_support.py b/examples/agents/langgraph/11_customer_support.py index 227e5509a..8762355ae 100644 --- a/examples/agents/langgraph/11_customer_support.py +++ b/examples/agents/langgraph/11_customer_support.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Customer Support Router — StateGraph with greet → classify → route → respond. Demonstrates: @@ -9,7 +6,7 @@ - Billing, technical, and general support branches Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -138,7 +135,7 @@ def handle_general(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.11_customer_support + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/12_code_agent.py b/examples/agents/langgraph/12_code_agent.py index 598762830..11307d31b 100644 --- a/examples/agents/langgraph/12_code_agent.py +++ b/examples/agents/langgraph/12_code_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Code Agent — create_agent with write_code, explain_code, and fix_bug tools. Demonstrates: @@ -9,7 +6,7 @@ - Multi-step tool usage: write then explain, or analyze then fix Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -152,7 +149,7 @@ def fix_bug(code: str, error_message: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.12_code_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/13_multi_turn.py b/examples/agents/langgraph/13_multi_turn.py index 9f1aee752..b046566b3 100644 --- a/examples/agents/langgraph/13_multi_turn.py +++ b/examples/agents/langgraph/13_multi_turn.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Multi-Turn Conversation — MemorySaver + session_id for continuity. Demonstrates: @@ -10,7 +7,7 @@ - A practical use case: interview preparation assistant Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -75,7 +72,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.13_multi_turn + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/14_qa_agent.py b/examples/agents/langgraph/14_qa_agent.py index cbd281332..5c277735c 100644 --- a/examples/agents/langgraph/14_qa_agent.py +++ b/examples/agents/langgraph/14_qa_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """QA Agent — StateGraph that retrieves context then generates an answer. Demonstrates: @@ -9,7 +6,7 @@ - Grounded answer generation using retrieved context Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -102,7 +99,7 @@ def generate_answer(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.14_qa_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/15_data_pipeline.py b/examples/agents/langgraph/15_data_pipeline.py index 812829682..081e2b76b 100644 --- a/examples/agents/langgraph/15_data_pipeline.py +++ b/examples/agents/langgraph/15_data_pipeline.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Data Pipeline — StateGraph with load → clean → analyze → report nodes. Demonstrates: @@ -9,7 +6,7 @@ - Using an LLM at the analysis and reporting stages Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -121,7 +118,7 @@ def generate_report(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.15_data_pipeline + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/16_parallel_branches.py b/examples/agents/langgraph/16_parallel_branches.py index 490913f02..23d90c3f4 100644 --- a/examples/agents/langgraph/16_parallel_branches.py +++ b/examples/agents/langgraph/16_parallel_branches.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Parallel Branches — StateGraph with two concurrent paths that merge. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: parallel pros/cons analysis Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -99,7 +96,7 @@ def merge_and_summarize(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.16_parallel_branches + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/17_error_recovery.py b/examples/agents/langgraph/17_error_recovery.py index bdeeb5d5f..40c6b6072 100644 --- a/examples/agents/langgraph/17_error_recovery.py +++ b/examples/agents/langgraph/17_error_recovery.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Error Recovery — StateGraph with try/except in nodes for graceful degradation. Demonstrates: @@ -10,7 +7,7 @@ - Conditional routing based on whether an error occurred Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -106,7 +103,7 @@ def recover_from_error(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.17_error_recovery + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/18_tools_condition.py b/examples/agents/langgraph/18_tools_condition.py index fd80469ab..9ec8437b1 100644 --- a/examples/agents/langgraph/18_tools_condition.py +++ b/examples/agents/langgraph/18_tools_condition.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """tools_condition — StateGraph using prebuilt tools_condition for ReAct routing. Demonstrates: @@ -9,7 +6,7 @@ - Practical use: a weather and timezone information agent Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -97,7 +94,7 @@ def agent(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.18_tools_condition + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/19_document_analysis.py b/examples/agents/langgraph/19_document_analysis.py index 97b7d9704..2f53bc8d0 100644 --- a/examples/agents/langgraph/19_document_analysis.py +++ b/examples/agents/langgraph/19_document_analysis.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Document Analysis Agent — create_agent with document processing tools. Demonstrates: @@ -9,7 +6,7 @@ - Chaining multiple tools to produce a comprehensive document report Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -174,7 +171,7 @@ def classify_sentiment(text: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.19_document_analysis + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/20_planner_agent.py b/examples/agents/langgraph/20_planner_agent.py index f3c0d8440..72a4665bf 100644 --- a/examples/agents/langgraph/20_planner_agent.py +++ b/examples/agents/langgraph/20_planner_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Planner Agent — StateGraph with plan → execute_steps → review pipeline. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: project breakdown and task execution Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -130,7 +127,7 @@ def review(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.20_planner_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/21_subgraph.py b/examples/agents/langgraph/21_subgraph.py index a9bcccd28..dc297a932 100644 --- a/examples/agents/langgraph/21_subgraph.py +++ b/examples/agents/langgraph/21_subgraph.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Subgraph — composing graphs within graphs. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: document processing pipeline with a nested analysis subgraph Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -134,7 +131,7 @@ def build_report(state: DocumentState) -> DocumentState: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.21_subgraph + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/22_human_in_the_loop.py b/examples/agents/langgraph/22_human_in_the_loop.py index b67b2c4fb..56c1d2174 100644 --- a/examples/agents/langgraph/22_human_in_the_loop.py +++ b/examples/agents/langgraph/22_human_in_the_loop.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Human-in-the-Loop — real human approval gate within a LangGraph workflow. Demonstrates: @@ -12,11 +9,11 @@ - Interactive streaming with schema-driven console prompts The workflow pauses at the review step and waits for a human to approve or -reject the draft via the AgentSpan UI or API. This is true human-in-the-loop, +reject the draft via the Conductor UI or API. This is true human-in-the-loop, not an LLM simulating a reviewer. Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ diff --git a/examples/agents/langgraph/23_retry_on_error.py b/examples/agents/langgraph/23_retry_on_error.py index cb487ad37..1587db26a 100644 --- a/examples/agents/langgraph/23_retry_on_error.py +++ b/examples/agents/langgraph/23_retry_on_error.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Retry on Error — automatic retry logic with exponential back-off. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: calling an unreliable external API with retries Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -80,7 +77,7 @@ def format_output(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.23_retry_on_error + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/24_map_reduce.py b/examples/agents/langgraph/24_map_reduce.py index b1d56661f..000c0ce5f 100644 --- a/examples/agents/langgraph/24_map_reduce.py +++ b/examples/agents/langgraph/24_map_reduce.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Map-Reduce — fan-out to parallel workers then aggregate results. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: analyzing multiple documents simultaneously Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -117,7 +114,7 @@ def reduce_summaries(state: OverallState) -> OverallState: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.24_map_reduce + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/25_supervisor.py b/examples/agents/langgraph/25_supervisor.py index 39d4a7d8f..0f4f1d5d4 100644 --- a/examples/agents/langgraph/25_supervisor.py +++ b/examples/agents/langgraph/25_supervisor.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Supervisor — multi-agent supervisor pattern. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: research → writing → editing pipeline with supervisor control Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -109,7 +106,7 @@ def route(state: State) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.25_supervisor + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/26_agent_handoff.py b/examples/agents/langgraph/26_agent_handoff.py index 1d63307af..49323755f 100644 --- a/examples/agents/langgraph/26_agent_handoff.py +++ b/examples/agents/langgraph/26_agent_handoff.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent Handoff — transferring control between specialized agents. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: customer service triage → billing / technical / general routing Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -128,7 +125,7 @@ def route_to_specialist(state: State) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.26_agent_handoff + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/27_persistent_memory.py b/examples/agents/langgraph/27_persistent_memory.py index c755fabc2..1cfdad56f 100644 --- a/examples/agents/langgraph/27_persistent_memory.py +++ b/examples/agents/langgraph/27_persistent_memory.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Persistent Memory — cross-session state via checkpointing. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: multi-turn chatbot that remembers earlier exchanges Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -63,7 +60,7 @@ def chat(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.27_persistent_memory + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/28_streaming_tokens.py b/examples/agents/langgraph/28_streaming_tokens.py index 7f0871797..df58cdb58 100644 --- a/examples/agents/langgraph/28_streaming_tokens.py +++ b/examples/agents/langgraph/28_streaming_tokens.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Streaming Tokens — streaming intermediate LLM output token by token. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: streaming a long-form answer to the terminal Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -62,7 +59,7 @@ def stream_to_console(prompt: str): # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.28_streaming_tokens + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/29_tool_categories.py b/examples/agents/langgraph/29_tool_categories.py index 17c96cee9..eb9baf7e2 100644 --- a/examples/agents/langgraph/29_tool_categories.py +++ b/examples/agents/langgraph/29_tool_categories.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tool Categories — organizing tools into categories with metadata. Demonstrates: @@ -10,7 +7,7 @@ - The LLM correctly selects the right tool for each query Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -135,7 +132,7 @@ def day_of_week(date_str: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.29_tool_categories + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/30_code_interpreter.py b/examples/agents/langgraph/30_code_interpreter.py index e4c1a29ce..cf8bfc91a 100644 --- a/examples/agents/langgraph/30_code_interpreter.py +++ b/examples/agents/langgraph/30_code_interpreter.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Code Interpreter — agent that writes and (safely) evaluates Python expressions. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: interactive Python tutor / coding assistant Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -137,7 +134,7 @@ def check_syntax(code: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.30_code_interpreter + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/31_classify_and_route.py b/examples/agents/langgraph/31_classify_and_route.py index b0204a22c..1acd9f873 100644 --- a/examples/agents/langgraph/31_classify_and_route.py +++ b/examples/agents/langgraph/31_classify_and_route.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Classify and Route — LLM-based input classification with specialized routing. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: smart help desk that routes to the right department Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -137,7 +134,7 @@ def route(state: State) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.31_classify_and_route + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/32_reflection_agent.py b/examples/agents/langgraph/32_reflection_agent.py index cd82b0d6b..6bb403bc9 100644 --- a/examples/agents/langgraph/32_reflection_agent.py +++ b/examples/agents/langgraph/32_reflection_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Reflection Agent — self-critique and iterative improvement. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: essay generation with quality self-improvement Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -111,7 +108,7 @@ def finalize(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.32_reflection_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/33_output_validator.py b/examples/agents/langgraph/33_output_validator.py index 6435a94c9..e4a7c5582 100644 --- a/examples/agents/langgraph/33_output_validator.py +++ b/examples/agents/langgraph/33_output_validator.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Output Validator — validate LLM output and retry until it meets criteria. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: ensuring the LLM always returns valid JSON Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -122,7 +119,7 @@ def finalize(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.33_output_validator + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/34_rag_pipeline.py b/examples/agents/langgraph/34_rag_pipeline.py index b2158ad9a..9884bfc7b 100644 --- a/examples/agents/langgraph/34_rag_pipeline.py +++ b/examples/agents/langgraph/34_rag_pipeline.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """RAG Pipeline — Retrieval-Augmented Generation with a StateGraph. Demonstrates: @@ -11,7 +8,7 @@ - Practical use case: Q&A over a private knowledge base Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -46,11 +43,11 @@ ), Document( page_content=( - "Agentspan provides a runtime for deploying LangGraph and LangChain agents at scale. " + "Conductor provides a runtime for deploying LangGraph and LangChain agents at scale. " "It uses Conductor as an orchestration engine and exposes agents as Conductor tasks. " "The AgentRuntime class handles worker registration and lifecycle management." ), - metadata={"source": "agentspan_docs", "topic": "agentspan"}, + metadata={"source": "Conductor_docs", "topic": "Conductor"}, ), Document( page_content=( @@ -174,7 +171,7 @@ def decide_to_generate(state: State) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.34_rag_pipeline + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/35_conversation_manager.py b/examples/agents/langgraph/35_conversation_manager.py index 854df9f20..1fbfcf930 100644 --- a/examples/agents/langgraph/35_conversation_manager.py +++ b/examples/agents/langgraph/35_conversation_manager.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Conversation Manager — advanced conversation history with summarization. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: long-running chatbot that handles context limits gracefully Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -121,7 +118,7 @@ def respond(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.35_conversation_manager + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/36_debate_agents.py b/examples/agents/langgraph/36_debate_agents.py index d73c094fd..433df4bf1 100644 --- a/examples/agents/langgraph/36_debate_agents.py +++ b/examples/agents/langgraph/36_debate_agents.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Debate Agents — two agents arguing opposing positions. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: pros/cons analysis, brainstorming, red-teaming Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -124,7 +121,7 @@ def continue_or_judge(state: State) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.36_debate_agents + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/37_document_grader.py b/examples/agents/langgraph/37_document_grader.py index a9a3267f1..963c1db91 100644 --- a/examples/agents/langgraph/37_document_grader.py +++ b/examples/agents/langgraph/37_document_grader.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Document Grader — score document relevance for a query. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: search result re-ranking and citation-based Q&A Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -120,7 +117,7 @@ def generate_answer(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.37_document_grader + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/38_state_machine.py b/examples/agents/langgraph/38_state_machine.py index 00daac2de..4385b6511 100644 --- a/examples/agents/langgraph/38_state_machine.py +++ b/examples/agents/langgraph/38_state_machine.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """State Machine — order processing workflow as an explicit state machine. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: e-commerce order processing pipeline Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -147,7 +144,7 @@ def route_after_payment(state: OrderState) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.38_state_machine + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/39_tool_call_chain.py b/examples/agents/langgraph/39_tool_call_chain.py index 4caa4c699..c6be00394 100644 --- a/examples/agents/langgraph/39_tool_call_chain.py +++ b/examples/agents/langgraph/39_tool_call_chain.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tool Call Chain — chaining multiple tool calls in sequence. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: data enrichment pipeline (fetch → transform → validate → summarize) Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -126,7 +123,7 @@ def agent(state: State) -> State: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.39_tool_call_chain + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/40_agent_as_tool.py b/examples/agents/langgraph/40_agent_as_tool.py index 3d961178f..80b26ed07 100644 --- a/examples/agents/langgraph/40_agent_as_tool.py +++ b/examples/agents/langgraph/40_agent_as_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent as Tool — using one compiled graph as a tool inside another agent. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: orchestrator dispatching to a math agent and a writing agent Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -133,7 +130,7 @@ def orchestrator(state: SimpleState) -> SimpleState: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.40_agent_as_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/41_react_agent_basic.py b/examples/agents/langgraph/41_react_agent_basic.py index 75f8d7537..d0a65fe7d 100644 --- a/examples/agents/langgraph/41_react_agent_basic.py +++ b/examples/agents/langgraph/41_react_agent_basic.py @@ -1,16 +1,13 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Basic ReAct Agent — create_react_agent runs on Conductor without create_agent. Demonstrates: - Using langgraph.prebuilt.create_react_agent directly with AgentRuntime - - No Agentspan wrapper needed — pass the graph straight to runtime.run() - - Agentspan detects the ReAct structure and runs LLM + tools on Conductor + - No Conductor wrapper needed — pass the graph straight to runtime.run() + - Conductor detects the ReAct structure and runs LLM + tools on Conductor (AI_MODEL task for the LLM, SIMPLE tasks per tool) Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -51,7 +48,7 @@ def reverse_string(text: str) -> str: llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) -# create_react_agent from langgraph.prebuilt — no Agentspan wrapper needed. +# create_react_agent from langgraph.prebuilt — no Conductor wrapper needed. # AgentRuntime automatically detects the "agent" + "tools" node structure, # extracts the LLM and tools, and runs them on Conductor as separate tasks. graph = create_react_agent(llm, tools=[calculate, count_words, reverse_string], name="math_and_text_agent") @@ -62,7 +59,7 @@ def reverse_string(text: str) -> str: graph, "What is sqrt(256) + 2**10? " "Also count the words in 'the quick brown fox jumps over the lazy dog'. " - "And what is 'Agentspan' reversed?", + "And what is 'Conductor' reversed?", ) print(f"Status: {result.status}") result.print_result() @@ -71,7 +68,7 @@ def reverse_string(text: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.41_react_agent_basic + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/42_react_agent_system_prompt.py b/examples/agents/langgraph/42_react_agent_system_prompt.py index bad2ee777..520d98460 100644 --- a/examples/agents/langgraph/42_react_agent_system_prompt.py +++ b/examples/agents/langgraph/42_react_agent_system_prompt.py @@ -1,16 +1,13 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """ReAct Agent with System Prompt — create_react_agent with prompt parameter. Demonstrates: - Passing a system prompt via the prompt parameter (LangGraph 1.x API) - - Agentspan extracts the system prompt and forwards it to the server + - Conductor extracts the system prompt and forwards it to the server as the agent's instructions — no information is lost - Custom persona carried through the full Conductor execution Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -65,7 +62,7 @@ def convert_units(value: float, from_unit: str, to_unit: str) -> str: llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # prompt sets the system prompt (LangGraph 1.x API, replaces state_modifier). -# Agentspan extracts the prompt from the graph's closure and forwards it +# Conductor extracts the prompt from the graph's closure and forwards it # to the server as the agent's instructions. graph = create_react_agent( llm, @@ -88,7 +85,7 @@ def convert_units(value: float, from_unit: str, to_unit: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.42_react_agent_system_prompt + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/43_react_agent_multi_model.py b/examples/agents/langgraph/43_react_agent_multi_model.py index dc866408f..623754835 100644 --- a/examples/agents/langgraph/43_react_agent_multi_model.py +++ b/examples/agents/langgraph/43_react_agent_multi_model.py @@ -1,15 +1,12 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """ReAct Agent — create_react_agent works with any LangChain-supported model. Demonstrates: - create_react_agent with ChatAnthropic (Claude) instead of OpenAI - Model is auto-detected from the LLM instance and forwarded to Conductor - - Same code, different model — no Agentspan-specific changes needed + - Same code, different model — no Conductor-specific changes needed Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - ANTHROPIC_API_KEY for ChatAnthropic """ @@ -56,7 +53,7 @@ def day_of_week(date_str: str) -> str: llm = ChatAnthropic(model="claude-haiku-4-5-20251001", temperature=0) -# Agentspan auto-detects ChatAnthropic and routes LLM calls through +# Conductor auto-detects ChatAnthropic and routes LLM calls through # the anthropic/ provider on the Conductor server. graph = create_react_agent(llm, tools=[get_today, days_between, day_of_week], name="date_calculator_agent") @@ -75,7 +72,7 @@ def day_of_week(date_str: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.43_react_agent_multi_model + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/44_context_condensation.py b/examples/agents/langgraph/44_context_condensation.py index dcbc6225f..92dbb7c3b 100644 --- a/examples/agents/langgraph/44_context_condensation.py +++ b/examples/agents/langgraph/44_context_condensation.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Context Condensation Stress Test — orchestrator + sub-agents, history condenses 3+ times. An orchestrator agent calls a ``deep_analyst`` sub-agent once per technology @@ -27,10 +24,10 @@ --------------------------------------------- Add to ``server/src/main/resources/application.properties`` and restart:: - agentspan.default-context-window=10000 + Conductor.default-context-window=10000 Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY """ @@ -358,7 +355,7 @@ def deep_analyst(domain: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.44_context_condensation + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/45_advanced_orchestration.py b/examples/agents/langgraph/45_advanced_orchestration.py index de0bd6e63..69e3ecb6f 100644 --- a/examples/agents/langgraph/45_advanced_orchestration.py +++ b/examples/agents/langgraph/45_advanced_orchestration.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Advanced Orchestration — LangGraph agent orchestrating a multi-step pipeline. Demonstrates: @@ -10,7 +7,7 @@ - Practical use case: automated business report generation from raw data inputs Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -184,7 +181,7 @@ def compile_report( # 1. Deploy once during CI/CD: # runtime.deploy(graph) # CLI alternative: - # agentspan deploy --package examples.langgraph.45_advanced_orchestration + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(graph) diff --git a/examples/agents/langgraph/46_crash_and_resume.py b/examples/agents/langgraph/46_crash_and_resume.py index d65ba7e32..adc112e50 100644 --- a/examples/agents/langgraph/46_crash_and_resume.py +++ b/examples/agents/langgraph/46_crash_and_resume.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Crash & Resume — restart workers after a process crash. Demonstrates the production pattern for durable LangGraph execution: @@ -22,7 +19,7 @@ resume logic, no execution_id needed. Why this matters: - LangGraph graphs running through Agentspan are compiled into durable + LangGraph graphs running through Conductor are compiled into durable Conductor workflows. If your process crashes (OOM, deploy, exception), no work is lost — the server holds the workflow state. You just need to restart serve() and the workers pick up from where they left off. @@ -38,7 +35,7 @@ runtime.start("sales_analyst", "prompt") # or via server API / UI Requirements: - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - OPENAI_API_KEY for ChatOpenAI """ @@ -51,8 +48,8 @@ from conductor.ai.agents import AgentRuntime -SESSION_FILE = "/tmp/agentspan_langgraph_resume.session" -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SESSION_FILE = "/tmp/Conductor_langgraph_resume.session" +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") UI_BASE = SERVER_URL.replace("/api", "") @@ -147,7 +144,7 @@ def generate_report(analysis: str) -> str: ui_link = f"{UI_BASE}/execution/{saved_execution_id}" print("-" * 60) -print("Open the Agentspan UI to see the execution in RUNNING state:") +print("Open the Conductor UI to see the execution in RUNNING state:") print(f" {ui_link}") print() print("The workflow is alive on the server but stalled — no workers are") diff --git a/examples/agents/langgraph/README.md b/examples/agents/langgraph/README.md index 58de06c4e..114bddf13 100644 --- a/examples/agents/langgraph/README.md +++ b/examples/agents/langgraph/README.md @@ -1,189 +1,8 @@ -# LangGraph Examples +# LangGraph Conductor-agent examples -46 examples demonstrating LangGraph integration with Agentspan, from hello-world to advanced multi-agent systems. +These examples run LangGraph graphs through the durable Conductor-agent runtime. +Install `conductor-python[langgraph]`, configure the server-side provider, and set +`CONDUCTOR_SERVER_URL` plus `CONDUCTOR_AGENT_LLM_MODEL` before running them. -## Prerequisites - -```bash -uv pip install langgraph langchain-core langchain-openai conductor-agent-sdk -``` - -| Package | Required | Notes | -|---------|----------|-------| -| `langgraph` | Yes | `StateGraph`, `create_react_agent`, `ToolNode`, `tools_condition` | -| `langchain-core` | Yes | Messages, tools, documents | -| `langchain-openai` | Yes | `ChatOpenAI` LLM provider | -| `langchain-anthropic` | Optional | Only for `43_react_agent_multi_model.py` (requires `ANTHROPIC_API_KEY`) | -| `pydantic` | Some examples | Used for structured output (08) | - -## Quick Start - -```bash -export AGENTSPAN_SERVER_URL=http://localhost:8080/api -export OPENAI_API_KEY=sk-... - -cd sdk/python -uv run python examples/langgraph/01_hello_world.py -``` - -## Examples - -### Basics (01–05) - -| # | File | Topic | -|---|------|-------| -| 01 | `01_hello_world.py` | Simplest agent with `create_agent`, no tools | -| 02 | `02_react_with_tools.py` | ReAct agent with `@tool` functions | -| 03 | `03_memory.py` | Multi-turn memory with `MemorySaver` + `session_id` | -| 04 | `04_simple_stategraph.py` | Custom `StateGraph` with typed state | -| 05 | `05_tool_node.py` | `ToolNode` + `tools_condition` standard loop | - -### Graph Patterns (06–10) - -| # | File | Topic | -|---|------|-------| -| 06 | `06_conditional_routing.py` | Conditional edges with routing functions | -| 07 | `07_system_prompt.py` | Custom system prompt via `prompt` parameter | -| 08 | `08_structured_output.py` | Structured/JSON output with `with_structured_output` | -| 09 | `09_math_agent.py` | Math tools (calculator, statistics) | -| 10 | `10_research_agent.py` | Multi-step research pipeline | - -### Domain Agents (11–15) - -| # | File | Topic | -|---|------|-------| -| 11 | `11_customer_support.py` | Customer service triage and response | -| 12 | `12_code_agent.py` | Code generation and explanation | -| 13 | `13_multi_turn.py` | Multi-turn conversation with history | -| 14 | `14_qa_agent.py` | Question answering with context | -| 15 | `15_data_pipeline.py` | Sequential data processing pipeline | - -### Advanced Patterns (16–20) - -| # | File | Topic | -|---|------|-------| -| 16 | `16_parallel_branches.py` | Parallel execution with `Send` API | -| 17 | `17_error_recovery.py` | Error handling and fallback nodes | -| 18 | `18_tools_condition.py` | Complex tool routing with multiple conditions | -| 19 | `19_document_analysis.py` | Multi-step document analysis | -| 20 | `20_planner_agent.py` | Plan → Execute → Review pipeline | - -### Composition & Reliability (21–25) - -| # | File | Topic | -|---|------|-------| -| 21 | `21_subgraph.py` | Nested subgraphs for modular composition | -| 22 | `22_human_in_the_loop.py` | Interrupt/resume with human approval | -| 23 | `23_retry_on_error.py` | Automatic retry with `RetryPolicy` | -| 24 | `24_map_reduce.py` | Fan-out / fan-in with `Send` API | -| 25 | `25_supervisor.py` | Supervisor orchestrating specialist agents | - -### Multi-Agent & Memory (26–30) - -| # | File | Topic | -|---|------|-------| -| 26 | `26_agent_handoff.py` | Explicit agent handoff (triage → specialist) | -| 27 | `27_persistent_memory.py` | Cross-session state with `MemorySaver` | -| 28 | `28_streaming_tokens.py` | Token-by-token streaming with `stream_mode="messages"` | -| 29 | `29_tool_categories.py` | Organized tool categories (math, string, date) | -| 30 | `30_code_interpreter.py` | Safe expression evaluation and code analysis | - -### Intelligence Patterns (31–35) - -| # | File | Topic | -|---|------|-------| -| 31 | `31_classify_and_route.py` | LLM-based classification + domain routing | -| 32 | `32_reflection_agent.py` | Generate → critique → improve loop | -| 33 | `33_output_validator.py` | Generate → validate → retry until schema passes | -| 34 | `34_rag_pipeline.py` | RAG with retrieve → grade → rewrite → generate | -| 35 | `35_conversation_manager.py` | Sliding window + auto-summarization | - -### Advanced Multi-Agent (36–40) - -| # | File | Topic | -|---|------|-------| -| 36 | `36_debate_agents.py` | Two agents arguing opposing positions | -| 37 | `37_document_grader.py` | Score + filter documents for relevance | -| 38 | `38_state_machine.py` | Order processing as an explicit state machine | -| 39 | `39_tool_call_chain.py` | Chaining sequential tool calls (ToolNode loop) | -| 40 | `40_agent_as_tool.py` | Compiled graph wrapped as `@tool` for orchestrators | - -### ReAct Variants & Production (41–46) - -| # | File | Topic | -|---|------|-------| -| 41 | `41_react_agent_basic.py` | Basic ReAct pattern | -| 42 | `42_react_agent_system_prompt.py` | ReAct with system prompt | -| 43 | `43_react_agent_multi_model.py` | Multi-model ReAct (OpenAI + Anthropic) | -| 44 | `44_context_condensation.py` | Orchestrator + sub-agent stress test | -| 45 | `45_advanced_orchestration.py` | Complex orchestration patterns | -| 46 | `46_crash_and_resume.py` | Crash recovery: resume execution after process restart | - -## Common Patterns - -### Basic `create_agent` (detected as `langgraph`) -```python -from langchain.agents import create_agent -from langchain_openai import ChatOpenAI -from langchain_core.tools import tool -from conductor.ai.agents import AgentRuntime - -llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) - -@tool -def my_tool(input: str) -> str: - """Tool description.""" - return f"Result: {input}" - -graph = create_agent(llm, tools=[my_tool], name="my_agent") - -with AgentRuntime() as runtime: - result = runtime.run(graph, "your prompt") - result.print_result() -``` - -### Custom `StateGraph` -```python -from langgraph.graph import StateGraph, START, END -from typing import TypedDict - -class State(TypedDict): - messages: list - result: str - -def my_node(state: State) -> State: - return {"result": "done"} - -builder = StateGraph(State) -builder.add_node("process", my_node) -builder.add_edge(START, "process") -builder.add_edge("process", END) -graph = builder.compile(name="my_graph") -``` - -### Session-based memory -```python -with AgentRuntime() as runtime: - result = runtime.run(graph, "prompt", session_id="user-123") -``` - -### Crash & Resume -```python -# Deploy once (CI/CD): -with AgentRuntime() as runtime: - runtime.deploy(graph) - -# Long-running worker process (restart on crash): -with AgentRuntime() as runtime: - runtime.serve(graph) # blocks, polls for tasks - -# After a crash, just restart serve() — workers reconnect, -# stalled tasks resume automatically. No special logic needed. -``` - -## Requirements - -- Python 3.11+ -- `uv` package manager -- `AGENTSPAN_SERVER_URL` — Agentspan server endpoint -- `OPENAI_API_KEY` — OpenAI API key for `ChatOpenAI` +Use the examples to explore graph execution, tools, state, streaming, and recovery. +See the [LangGraph guide](../../../docs/agents/frameworks/langgraph.md). diff --git a/examples/agents/openai/01_basic_agent.py b/examples/agents/openai/01_basic_agent.py index a157d3a08..92e6bd4cf 100644 --- a/examples/agents/openai/01_basic_agent.py +++ b/examples/agents/openai/01_basic_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Basic OpenAI Agent — simplest possible agent with no tools. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from agents import Agent @@ -38,7 +35,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.openai.01_basic_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/openai/02_function_tools.py b/examples/agents/openai/02_function_tools.py index 1a864fe00..a941fc000 100644 --- a/examples/agents/openai/02_function_tools.py +++ b/examples/agents/openai/02_function_tools.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent with Function Tools — tool calling via @function_tool. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from agents import Agent, function_tool @@ -86,7 +83,7 @@ def lookup_population(city: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.openai.02_function_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/openai/03_structured_output.py b/examples/agents/openai/03_structured_output.py index 2d99c1212..bb21de409 100644 --- a/examples/agents/openai/03_structured_output.py +++ b/examples/agents/openai/03_structured_output.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent with Structured Output — enforced JSON schema response. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install openai-agents pydantic - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from typing import List @@ -65,7 +62,7 @@ class MovieList(BaseModel): # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.openai.03_structured_output + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/openai/04_handoffs.py b/examples/agents/openai/04_handoffs.py index 1feecaa68..ccdf8df2a 100644 --- a/examples/agents/openai/04_handoffs.py +++ b/examples/agents/openai/04_handoffs.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent Handoffs — multi-agent orchestration with handoffs. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from agents import Agent, function_tool @@ -114,7 +111,7 @@ def get_product_info(product_name: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(triage_agent) # CLI alternative: - # agentspan deploy --package examples.openai.04_handoffs + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(triage_agent) diff --git a/examples/agents/openai/05_guardrails.py b/examples/agents/openai/05_guardrails.py index 6c5bb3ff9..b7352a9ff 100644 --- a/examples/agents/openai/05_guardrails.py +++ b/examples/agents/openai/05_guardrails.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent with Guardrails — input and output validation. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from agents import ( @@ -130,7 +127,7 @@ def check_output_safety(ctx, agent, output) -> GuardrailFunctionOutput: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.openai.05_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/openai/06_model_settings.py b/examples/agents/openai/06_model_settings.py index cba2dfcf3..014ca7ccd 100644 --- a/examples/agents/openai/06_model_settings.py +++ b/examples/agents/openai/06_model_settings.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent with Model Settings — temperature, max tokens, and more. Demonstrates: @@ -12,8 +9,8 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from agents import Agent, ModelSettings @@ -64,7 +61,7 @@ # 1. Deploy once during CI/CD: # runtime.deploy(creative_agent) # CLI alternative: - # agentspan deploy --package examples.openai.06_model_settings + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(creative_agent) diff --git a/examples/agents/openai/07_streaming.py b/examples/agents/openai/07_streaming.py index eefa468fb..339458882 100644 --- a/examples/agents/openai/07_streaming.py +++ b/examples/agents/openai/07_streaming.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent with Streaming — real-time event streaming. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from agents import Agent, function_tool @@ -61,7 +58,7 @@ def search_knowledge_base(query: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.openai.07_streaming + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/openai/08_agent_as_tool.py b/examples/agents/openai/08_agent_as_tool.py index fc07c369c..da089c45c 100644 --- a/examples/agents/openai/08_agent_as_tool.py +++ b/examples/agents/openai/08_agent_as_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent — Manager Pattern with agents-as-tools. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from agents import Agent, function_tool @@ -107,7 +104,7 @@ def extract_keywords(text: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(manager) # CLI alternative: - # agentspan deploy --package examples.openai.08_agent_as_tool + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(manager) diff --git a/examples/agents/openai/09_dynamic_instructions.py b/examples/agents/openai/09_dynamic_instructions.py index dbf330a83..8b59b0371 100644 --- a/examples/agents/openai/09_dynamic_instructions.py +++ b/examples/agents/openai/09_dynamic_instructions.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent with Dynamic Instructions — callable instruction function. Demonstrates: @@ -11,8 +8,8 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integration configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable """ from datetime import datetime @@ -80,7 +77,7 @@ def add_todo(task: str, priority: str = "medium") -> str: # 1. Deploy once during CI/CD: # runtime.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.openai.09_dynamic_instructions + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(agent) diff --git a/examples/agents/openai/10_multi_model.py b/examples/agents/openai/10_multi_model.py index 344f6d8fe..880c7f4f2 100644 --- a/examples/agents/openai/10_multi_model.py +++ b/examples/agents/openai/10_multi_model.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agent — Multi-Model Handoff with different LLMs. Demonstrates: @@ -11,9 +8,9 @@ Requirements: - pip install openai-agents - Conductor server with OpenAI LLM integrations configured - - AGENTSPAN_SERVER_URL=http://localhost:8080/api as environment variable - - AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini as environment variable - - AGENTSPAN_SECONDARY_LLM_MODEL=openai/gpt-4o as environment variable + - CONDUCTOR_SERVER_URL=http://localhost:8080/api as environment variable + - CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini as environment variable + - CONDUCTOR_AGENT_SECONDARY_LLM_MODEL=openai/gpt-4o as environment variable """ from agents import Agent, ModelSettings, function_tool @@ -116,7 +113,7 @@ def generate_code_sample(language: str, topic: str) -> str: # 1. Deploy once during CI/CD: # runtime.deploy(triage) # CLI alternative: - # agentspan deploy --package examples.openai.10_multi_model + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # runtime.serve(triage) diff --git a/examples/agents/openai/README.md b/examples/agents/openai/README.md index 723b7f9c4..ed1ecd328 100644 --- a/examples/agents/openai/README.md +++ b/examples/agents/openai/README.md @@ -1,70 +1,10 @@ -# OpenAI Agent SDK Examples +# OpenAI Agents SDK-style Conductor-agent examples -These examples demonstrate running agents written with the [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) (`openai-agents`) on the Agentspan runtime. +These examples show OpenAI Agents SDK-style agents running on the durable +Conductor runtime. Install `conductor-python[openai-agents]`, configure the +provider on the Conductor server, and set `CONDUCTOR_SERVER_URL` and +`CONDUCTOR_AGENT_LLM_MODEL`. -The agents are defined using standard OpenAI SDK classes and decorators — Agentspan auto-detects the framework, serializes the agent generically, and the server normalizes the config into an agent execution. **Zero translation code in the SDK.** - -## Prerequisites - -```bash -uv pip install openai-agents conductor-agent-sdk -``` - -| Package | Required | Notes | -|---------|----------|-------| -| `openai-agents` | Yes | `Agent`, `function_tool`, `ModelSettings`, guardrails | -| `pydantic` | Some examples | Used for structured output (03) | - -Export environment variables: - -```bash -export AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini -export AGENTSPAN_SERVER_URL=http://localhost:8080/api -export OPENAI_API_KEY=your-key -``` - -## Examples - -| # | File | Feature | Description | -|---|------|---------|-------------| -| 01 | [01_basic_agent.py](01_basic_agent.py) | **Basic Agent** | Simplest agent — single LLM, no tools. Shows auto-detection and server normalization. | -| 02 | [02_function_tools.py](02_function_tools.py) | **Function Tools** | Multiple `@function_tool` decorated functions with typed parameters. Tools are auto-extracted as Conductor workers. | -| 03 | [03_structured_output.py](03_structured_output.py) | **Structured Output** | Pydantic `output_type` for enforced JSON schema responses. Combined with `ModelSettings`. | -| 04 | [04_handoffs.py](04_handoffs.py) | **Handoffs** | Multi-agent orchestration with triage → specialist handoffs. Maps to Conductor's `strategy="handoff"`. | -| 05 | [05_guardrails.py](05_guardrails.py) | **Guardrails** | Input guardrails (PII detection) and output guardrails (safety filtering). Guardrail functions become Conductor workers. | -| 06 | [06_model_settings.py](06_model_settings.py) | **Model Settings** | `ModelSettings` for temperature and max_tokens tuning. Creative vs. precise agents. | -| 07 | [07_streaming.py](07_streaming.py) | **Streaming** | Default `runtime.run()` flow with a commented `runtime.stream()` alternative for SSE events. | -| 08 | [08_agent_as_tool.py](08_agent_as_tool.py) | **Agent-as-Tool** | Manager pattern with `Agent.as_tool()`. Manager retains control and synthesizes specialist results. | -| 09 | [09_dynamic_instructions.py](09_dynamic_instructions.py) | **Dynamic Instructions** | Callable instruction function that generates context-aware prompts (time-of-day, user preferences). | -| 10 | [10_multi_model.py](10_multi_model.py) | **Multi-Model** | Multiple agents with shared `settings.llm_model`. Override via `AGENTSPAN_LLM_MODEL` env var. | - -## Feature Coverage - -| OpenAI SDK Feature | Example(s) | -|---|---| -| `Agent` class | All | -| `@function_tool` decorator | 02, 04, 05, 07, 08, 09, 10 | -| `handoffs` | 04, 10 | -| `output_type` (structured output) | 03 | -| `ModelSettings` (temperature, max_tokens) | 03, 06, 10 | -| `InputGuardrail` / `OutputGuardrail` | 05 | -| `Agent.as_tool()` (manager pattern) | 08 | -| Dynamic instructions (callable) | 09 | -| Multiple models | 10 | -| Streaming (`runtime.stream()`, commented alternative) | 07 | -| Multi-agent patterns | 04, 08, 10 | - -## How It Works - -``` -OpenAI Agent object - │ - ▼ (auto-detected by type(agent).__module__ == "agents") -Generic serializer → JSON dict + callable extraction - │ - ▼ POST /api/agent/start { framework: "openai", rawConfig: {...} } -Server OpenAINormalizer → AgentConfig → Conductor WorkflowDef - │ - ▼ -Agentspan runtime executes the agent -``` +The bridge preserves familiar agent, tool, handoff, and streaming shapes while +Conductor records workflow state and tool execution. See the +[framework guide](../../../docs/agents/frameworks/openai.md). diff --git a/examples/agents/openai/run_all.py b/examples/agents/openai/run_all.py index 21de9c58f..12f68d5a6 100644 --- a/examples/agents/openai/run_all.py +++ b/examples/agents/openai/run_all.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Run all OpenAI agent examples and verify correctness. Usage: diff --git a/examples/agents/quickstart/01_basic_agent.py b/examples/agents/quickstart/01_basic_agent.py index b39d8c0b3..b155e4561 100644 --- a/examples/agents/quickstart/01_basic_agent.py +++ b/examples/agents/quickstart/01_basic_agent.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Basic agent — the simplest possible agentspan example.""" +"""Basic agent — the simplest possible Conductor example.""" from conductor.ai.agents import Agent, AgentRuntime @@ -20,7 +20,7 @@ # 1. Deploy once during CI/CD: # rt.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.quickstart.01_basic_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(agent) diff --git a/examples/agents/quickstart/02_tools.py b/examples/agents/quickstart/02_tools.py index e1283a7f5..934514ae5 100644 --- a/examples/agents/quickstart/02_tools.py +++ b/examples/agents/quickstart/02_tools.py @@ -28,7 +28,7 @@ def get_weather(city: str) -> str: # 1. Deploy once during CI/CD: # rt.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.quickstart.02_tools + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(agent) diff --git a/examples/agents/quickstart/03_multi_agent.py b/examples/agents/quickstart/03_multi_agent.py index 2040746da..9490217fd 100644 --- a/examples/agents/quickstart/03_multi_agent.py +++ b/examples/agents/quickstart/03_multi_agent.py @@ -30,7 +30,7 @@ # 1. Deploy once during CI/CD: # rt.deploy(pipeline) # CLI alternative: - # agentspan deploy --package examples.quickstart.03_multi_agent + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(pipeline) diff --git a/examples/agents/quickstart/04_guardrails.py b/examples/agents/quickstart/04_guardrails.py index 29e9ae48f..9191831cd 100644 --- a/examples/agents/quickstart/04_guardrails.py +++ b/examples/agents/quickstart/04_guardrails.py @@ -28,7 +28,7 @@ # 1. Deploy once during CI/CD: # rt.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.quickstart.04_guardrails + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(agent) diff --git a/examples/agents/quickstart/05_claude_code.py b/examples/agents/quickstart/05_claude_code.py index 75ca49dc2..a5f70c156 100644 --- a/examples/agents/quickstart/05_claude_code.py +++ b/examples/agents/quickstart/05_claude_code.py @@ -20,7 +20,7 @@ # 1. Deploy once during CI/CD: # rt.deploy(agent) # CLI alternative: - # agentspan deploy --package examples.quickstart.05_claude_code + # runtime.deploy(agent) from a release script # # 2. In a separate long-lived worker process: # rt.serve(agent) diff --git a/examples/agents/quickstart/run_all.py b/examples/agents/quickstart/run_all.py index ecf15ec52..208125b17 100644 --- a/examples/agents/quickstart/run_all.py +++ b/examples/agents/quickstart/run_all.py @@ -47,7 +47,7 @@ def _load_module(filename: str): # ── Config ────────────────────────────────────────────── TIMEOUT_SECONDS = 30 -SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") @dataclass @@ -85,7 +85,7 @@ def fetch_execution(execution_id: str) -> dict: return resp.json() -# System task types managed by Conductor/AgentSpan — everything else is a tool worker task +# System task types managed by Conductor/Conductor — everything else is a tool worker task SYSTEM_TASK_TYPES = { "LLM_CHAT_COMPLETE", "SET_VARIABLE", "DO_WHILE", "SWITCH", "FORK", "JOIN", "INLINE", "SUB_WORKFLOW", "HUMAN", "TERMINATE", "WAIT", "EVENT", diff --git a/examples/agents/run_all_examples.py b/examples/agents/run_all_examples.py index 74bc84b0b..78417bcb9 100644 --- a/examples/agents/run_all_examples.py +++ b/examples/agents/run_all_examples.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. -"""Run every agent example against a live Agentspan server and report status. +"""Run every agent example against a live Conductor server and report status. "Works fine" means: * PASS — process exits 0 within the timeout (no compile/import/runtime error, @@ -24,8 +22,8 @@ [--only PATTERN] [--out report.html] Env (with sensible defaults for the local dev server on :6767): - CONDUCTOR_SERVER_URL / AGENTSPAN_SERVER_URL server API base - AGENTSPAN_LLM_MODEL model steered to a working provider + CONDUCTOR_SERVER_URL / CONDUCTOR_SERVER_URL server API base + CONDUCTOR_AGENT_LLM_MODEL model steered to a working provider """ from __future__ import annotations @@ -255,13 +253,13 @@ def main() -> int: ap.add_argument("--out", default=str(HERE / "run_report.html")) args = ap.parse_args() - server = os.environ.get("CONDUCTOR_SERVER_URL") or os.environ.get("AGENTSPAN_SERVER_URL") or DEFAULT_SERVER - model = os.environ.get("AGENTSPAN_LLM_MODEL", DEFAULT_MODEL) + server = os.environ.get("CONDUCTOR_SERVER_URL") or os.environ.get("CONDUCTOR_SERVER_URL") or DEFAULT_SERVER + model = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", DEFAULT_MODEL) env = dict(os.environ) env["CONDUCTOR_SERVER_URL"] = server - env["AGENTSPAN_SERVER_URL"] = server - env["AGENTSPAN_LLM_MODEL"] = model + env["CONDUCTOR_SERVER_URL"] = server + env["CONDUCTOR_AGENT_LLM_MODEL"] = model env["PYTHONPATH"] = os.pathsep.join([str(REPO / "src"), str(HERE), env.get("PYTHONPATH", "")]) env["PYTHONUNBUFFERED"] = "1" diff --git a/examples/agents/settings.py b/examples/agents/settings.py index 2f03abe0b..0037ca423 100644 --- a/examples/agents/settings.py +++ b/examples/agents/settings.py @@ -1,17 +1,14 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Shared settings for all examples. -Set ``AGENTSPAN_LLM_MODEL`` as an environment variable to override the +Set ``CONDUCTOR_AGENT_LLM_MODEL`` as an environment variable to override the default model used by all examples:: - export AGENTSPAN_LLM_MODEL=anthropic/claude-sonnet-4-20250514 - export AGENTSPAN_LLM_MODEL=google_gemini/gemini-2.0-flash + export CONDUCTOR_AGENT_LLM_MODEL=anthropic/claude-sonnet-4-20250514 + export CONDUCTOR_AGENT_LLM_MODEL=google_gemini/gemini-2.0-flash If unset, defaults to ``anthropic/claude-sonnet-4-6``. -``AGENTSPAN_SECONDARY_LLM_MODEL`` provides a second model for multi-model examples +``CONDUCTOR_AGENT_SECONDARY_LLM_MODEL`` provides a second model for multi-model examples (e.g., cheap triage vs capable specialist). Defaults to ``openai/gpt-4o``. """ @@ -27,8 +24,14 @@ class Settings: @classmethod def from_env(cls) -> "Settings": return cls( - llm_model=os.environ.get("AGENTSPAN_LLM_MODEL", "anthropic/claude-sonnet-4-6"), - secondary_llm_model=os.environ.get("AGENTSPAN_SECONDARY_LLM_MODEL", "openai/gpt-4o"), + llm_model=( + os.environ.get("CONDUCTOR_AGENT_LLM_MODEL") + or "openai/gpt-4o" + ), + secondary_llm_model=( + os.environ.get("CONDUCTOR_AGENT_SECONDARY_LLM_MODEL") + or "openai/gpt-4o" + ), ) diff --git a/examples/agents/testing_multi_agent_correctness.py b/examples/agents/testing_multi_agent_correctness.py index 75d8f2ba3..4b871b064 100644 --- a/examples/agents/testing_multi_agent_correctness.py +++ b/examples/agents/testing_multi_agent_correctness.py @@ -1,13 +1,10 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """ Testing Multi-Agent Correctness ================================ This file demonstrates how to write correctness tests for every multi-agent -strategy in Agentspan. Each section shows: +strategy in Conductor. Each section shows: 1. How the agent is defined 2. What "correct behavior" means for that strategy @@ -17,7 +14,7 @@ Run the mock tests with: pytest examples/testing_multi_agent_correctness.py -v -The live tests require a running Agentspan server and are marked with +The live tests require a running Conductor server and are marked with @pytest.mark.integration. """ @@ -1032,7 +1029,7 @@ def test_output_guardrail_catches_bad_response(self): # 10. LIVE TESTS (require running server) # ═══════════════════════════════════════════════════════════════════════ # -# These tests run against a real Agentspan server with real LLM calls. +# These tests run against a real Conductor server with real LLM calls. # The assertions are more tolerant — checking behavior patterns rather # than exact strings. # ═══════════════════════════════════════════════════════════════════════ @@ -1408,7 +1405,7 @@ class TestEvalRunnerLive: @pytest.fixture def runtime(self): """Skip if no server available.""" - pytest.skip("Requires running Agentspan server") + pytest.skip("Requires running Conductor server") def test_handoff_eval(self, runtime): """Run eval suite for handoff correctness.""" @@ -1475,7 +1472,7 @@ class TestLiveMultiAgent: @pytest.fixture def runtime(self): """Skip if no server available.""" - pytest.skip("Requires running Agentspan server") + pytest.skip("Requires running Conductor server") # from conductor.ai.agents import AgentRuntime # rt = AgentRuntime() # yield rt diff --git a/pyproject.toml b/pyproject.toml index 3c4dca238..3c2d22329 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ urllib3 = ">=2.6.3" cloudpickle = ">=2.0" # Agent framework integrations — optional, activated via [tool.poetry.extras] below. -# Floors raised to the versions Agentspan's own uv.lock proved mutually compatible +# Floors raised to the versions ConductorAgent's own uv.lock proved mutually compatible # (langchain's 0.3.x-floor collided with today's published 1.x line during resolution). langchain = { version = ">=1.2.13", optional = true } langchain-core = { version = ">=1.2.20", optional = true } @@ -78,9 +78,10 @@ pytest-asyncio = ">=0.21" pytest-xdist = ">=3.0" pytest-rerunfailures = ">=14.0" mypy = ">=1.10" +jsonschema = ">=4.23" [tool.poetry.plugins."pytest11"] -agentspan-testing = "conductor.ai.agents.testing.pytest_plugin" +conductor-agents-testing = "conductor.ai.agents.testing.pytest_plugin" [tool.ruff] target-version = "py310" @@ -192,9 +193,9 @@ line-ending = "auto" [tool.pytest.ini_options] pythonpath = ["src"] markers = [ - "integration: requires a live Agentspan-compatible server", - "e2e: requires a live server and AGENTSPAN_SERVER_URL", - "sse: requires SSE streaming (AGENTSPAN_STREAMING_ENABLED=true)", + "integration: requires a live Conductor Agents-compatible server", + "e2e: requires a live server and CONDUCTOR_SERVER_URL", + "sse: requires SSE streaming (CONDUCTOR_AGENT_STREAMING_ENABLED=true)", "agent_correctness: marks tests as agent correctness tests", "semantic: marks tests that use an LLM judge for semantic assertions", "slow_sync: long-running sync lease-extension tests (~90s)", diff --git a/scripts/package-e2e-bundle.sh b/scripts/package-e2e-bundle.sh index 6732199a1..43fab234d 100755 --- a/scripts/package-e2e-bundle.sh +++ b/scripts/package-e2e-bundle.sh @@ -9,9 +9,8 @@ set -euo pipefail # # Downstream repos (e.g. orkes-io/orkes-conductor) download the bundle from # the python-sdk GitHub release and run it against their own server build. -# This replaces the agentspan-sdk-e2e-python-* bundles formerly cut from -# agentspan-ai/agentspan — python-sdk is now the canonical home of these -# suites. Mirrors conductor-oss/java-sdk (conductor-ai-e2e/release/) and +# The python-sdk is the canonical home of these suites. Mirrors +# conductor-oss/java-sdk (conductor-ai-e2e/release/) and # conductor-oss/javascript-sdk (scripts/). # # Usage: @@ -70,16 +69,14 @@ cat > "$STAGE/run.sh" <<'EOF' #!/usr/bin/env bash set -euo pipefail # Runs the agent e2e suite against a live Conductor server with the agent -# runtime enabled (conductor-oss >= 3.32.0-rc.8, or orkes-conductor with -# agentspan.embedded=true). +# runtime enabled (conductor-oss >= 3.32.0-rc.8). # # Required services (NOT started by this script): -# - Conductor server → AGENTSPAN_SERVER_URL (default http://localhost:8080/api) +# - Conductor server → CONDUCTOR_SERVER_URL (default http://localhost:8080/api) # - mcp-testkit → MCP_TESTKIT_URL (default http://localhost:3001) # Optional: -# - AGENTSPAN_LLM_MODEL (default openai/gpt-4o-mini); the provider API key +# - CONDUCTOR_AGENT_LLM_MODEL (default openai/gpt-4o-mini); the provider API key # must be configured on the SERVER — the suites never read it. -# - AGENTSPAN_CLI_PATH (default `agentspan` on PATH) — CLI suites skip if absent. # # Requires python 3.10–3.13 on PATH as `python` (use a venv; the harness deps # may not build on newer interpreters). Usage: ./run.sh [extra pytest args] @@ -101,23 +98,20 @@ Self-contained end-to-end tests for the Conductor Python agent SDK, pinned to release **@VERSION@**. Resolves `conductor-python[agents]==@VERSION@` from PyPI — no SDK source is vendored. Cut from [conductor-oss/python-sdk](https://github.com/conductor-oss/python-sdk) -(`e2e/`); supersedes the `agentspan-sdk-e2e-python-*` bundles formerly -released from agentspan-ai/agentspan. +(`e2e/`). ## Prerequisites (you provide these) | Requirement | Env var | Default | |-----------------------------------|------------------------|-----------------------------| | python 3.10–3.13 (use a venv) | — | — | -| Conductor server w/ agent runtime | `AGENTSPAN_SERVER_URL` | `http://localhost:8080/api` | -| LLM model | `AGENTSPAN_LLM_MODEL` | `openai/gpt-4o-mini` | +| Conductor server w/ agent runtime | `CONDUCTOR_SERVER_URL` | `http://localhost:8080/api` | +| LLM model | `CONDUCTOR_AGENT_LLM_MODEL` | `openai/gpt-4o-mini` | | mcp-testkit (MCP suites) | `MCP_TESTKIT_URL` | `http://localhost:3001` | -| agentspan CLI (CLI suites) | `AGENTSPAN_CLI_PATH` | `agentspan` (on `PATH`) | -The server needs the agent runtime: conductor-oss `>= 3.32.0-rc.8`, or -orkes-conductor booted with `agentspan.embedded=true`. LLM provider API keys +The server needs the agent runtime: conductor-oss `>= 3.32.0-rc.8`. LLM provider API keys (e.g. `OPENAI_API_KEY`) go to the **server** process, not this suite. -Suites that need an absent optional service (CLI, LangGraph) skip rather +Suites that need an absent optional service (LangGraph) skip rather than fail. ## Run diff --git a/scripts/run_examples.sh b/scripts/run_examples.sh index 10001d1e4..3a50b6d4f 100755 --- a/scripts/run_examples.sh +++ b/scripts/run_examples.sh @@ -8,7 +8,7 @@ # ./scripts/run_examples.sh 01 02 10 # run only matching examples (prefix match) # # Requires: -# export AGENTSPAN_SERVER_URL=http://localhost:8080/api +# export CONDUCTOR_SERVER_URL=http://localhost:8080/api # # Exit code: 0 if all passed, 1 if any failed. @@ -213,7 +213,7 @@ if [[ ${#FAILED[@]} -gt 0 ]]; then fi # Query Conductor for workflow status if we have an ID and server URL - if [[ -n "$WF_ID" && -n "${AGENTSPAN_SERVER_URL:-}" ]]; then + if [[ -n "$WF_ID" && -n "${CONDUCTOR_SERVER_URL:-}" ]]; then echo " Workflow: $WF_ID" # Use the SDK's own client so auth (key/secret → token) is handled WF_INFO=$($PYTHON -c " @@ -262,7 +262,7 @@ except Exception as e: fi elif [[ -n "$WF_ID" ]]; then echo " Workflow: $WF_ID" - echo " (set AGENTSPAN_SERVER_URL to query workflow status)" + echo " (set CONDUCTOR_SERVER_URL to query workflow status)" fi fi done diff --git a/src/conductor/ai/__init__.py b/src/conductor/ai/__init__.py index 380b72ba8..10a3154b2 100644 --- a/src/conductor/ai/__init__.py +++ b/src/conductor/ai/__init__.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - # OpenAI Agents SDK compatibility — ``from conductor.ai import Runner`` from conductor.ai.agents.openai_compat import Runner, RunResult from conductor.ai.agents.tool import tool as function_tool diff --git a/src/conductor/ai/agents/__init__.py b/src/conductor/ai/agents/__init__.py index 6bdeda041..fce5b308a 100644 --- a/src/conductor/ai/agents/__init__.py +++ b/src/conductor/ai/agents/__init__.py @@ -1,7 +1,4 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - -"""Agentspan Agents SDK — durable, scalable, observable AI agents. +"""Conductor Agent Python SDK — durable, scalable, observable AI agents. This is the public API surface. Import everything you need from here:: @@ -55,7 +52,11 @@ def get_weather(city: str) -> str: ) # Exceptions -from conductor.ai.agents.exceptions import AgentAPIError, AgentNotFoundError, AgentspanError +from conductor.ai.agents.exceptions import ( + AgentAPIError, + AgentNotFoundError, + ConductorAgentError, +) # Extended agent types from conductor.ai.agents.ext import GPTAssistantAgent @@ -344,7 +345,7 @@ def resolve_credentials(task: object, names: list) -> dict: "OnTextMention", "OnCondition", # Exceptions - "AgentspanError", + "ConductorAgentError", "AgentAPIError", "AgentNotFoundError", # Agent discovery diff --git a/src/conductor/ai/agents/_internal/__init__.py b/src/conductor/ai/agents/_internal/__init__.py index 3729bd063..3919e0d38 100644 --- a/src/conductor/ai/agents/_internal/__init__.py +++ b/src/conductor/ai/agents/_internal/__init__.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - # Private utilities — not part of the public API. from __future__ import annotations diff --git a/src/conductor/ai/agents/_internal/agent_http.py b/src/conductor/ai/agents/_internal/agent_http.py index 2c0e06c7e..3318f5119 100644 --- a/src/conductor/ai/agents/_internal/agent_http.py +++ b/src/conductor/ai/agents/_internal/agent_http.py @@ -1,7 +1,4 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - -"""Authenticated HTTP to Agentspan ``/agent/*`` endpoints via the SDK ``ApiClient``. +"""Authenticated HTTP to Conductor-agent ``/agent/*`` endpoints via the SDK ``ApiClient``. Framework workers (claude_agent_sdk, langchain, langgraph) run in spawned worker processes and receive ``(server_url, auth_key, auth_secret)`` as plain strings — a @@ -72,7 +69,7 @@ def agent_post( *, read_response: bool = False, ) -> Optional[Any]: - """POST to an Agentspan ``/agent/*`` endpoint through the SDK ``ApiClient``. + """POST to a Conductor-agent ``/agent/*`` endpoint through the SDK ``ApiClient``. ``path`` is relative to the server host (which already ends in ``/api``), so it must start with ``/`` and omit ``/api`` — e.g. ``/agent/events/{id}``. diff --git a/src/conductor/ai/agents/_internal/model_parser.py b/src/conductor/ai/agents/_internal/model_parser.py index 18dccd72c..90ab26c7e 100644 --- a/src/conductor/ai/agents/_internal/model_parser.py +++ b/src/conductor/ai/agents/_internal/model_parser.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Parse ``"provider/model"`` strings into (provider, model) tuples. Conductor's :class:`LlmChatComplete` requires separate ``llm_provider`` and diff --git a/src/conductor/ai/agents/_internal/provider_registry.py b/src/conductor/ai/agents/_internal/provider_registry.py index d99ca809d..981c14fae 100644 --- a/src/conductor/ai/agents/_internal/provider_registry.py +++ b/src/conductor/ai/agents/_internal/provider_registry.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Provider registry — metadata for auto-registering LLM integrations. Maps known LLM provider names (as used in ``"provider/model"`` strings) to the diff --git a/src/conductor/ai/agents/_internal/schema_utils.py b/src/conductor/ai/agents/_internal/schema_utils.py index 91f3dfe82..f85813884 100644 --- a/src/conductor/ai/agents/_internal/schema_utils.py +++ b/src/conductor/ai/agents/_internal/schema_utils.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """JSON Schema generation from Python type hints, Pydantic models, and dataclasses. Wraps ``conductor.client.automator.json_schema_generator`` and adds support diff --git a/src/conductor/ai/agents/_internal/token_utils.py b/src/conductor/ai/agents/_internal/token_utils.py index 18d614c17..b178b59e8 100644 --- a/src/conductor/ai/agents/_internal/token_utils.py +++ b/src/conductor/ai/agents/_internal/token_utils.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """JWT helper for the agent runtime. Auth-token minting/caching lives in the SDK ``ApiClient`` (a single token diff --git a/src/conductor/ai/agents/agent.py b/src/conductor/ai/agents/agent.py index f79f9dcaa..b199aa115 100644 --- a/src/conductor/ai/agents/agent.py +++ b/src/conductor/ai/agents/agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent — the single orchestration primitive for Conductor Agents. Everything is an Agent. A single agent wraps an LLM + tools. diff --git a/src/conductor/ai/agents/callback.py b/src/conductor/ai/agents/callback.py index bc2eaadb0..b03f7924f 100644 --- a/src/conductor/ai/agents/callback.py +++ b/src/conductor/ai/agents/callback.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """CallbackHandler — composable lifecycle hooks for agents. Provides a base class for hooking into agent, model, and tool lifecycle diff --git a/src/conductor/ai/agents/cli_config.py b/src/conductor/ai/agents/cli_config.py index 47b0147f1..7fac5fe5e 100644 --- a/src/conductor/ai/agents/cli_config.py +++ b/src/conductor/ai/agents/cli_config.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """First-class CLI command execution configuration for agents. Provides :class:`CliConfig` for declarative CLI tool attachment on diff --git a/src/conductor/ai/agents/code_execution_config.py b/src/conductor/ai/agents/code_execution_config.py index 0a2bb7286..c060e22bd 100644 --- a/src/conductor/ai/agents/code_execution_config.py +++ b/src/conductor/ai/agents/code_execution_config.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """First-class code execution configuration for agents. Provides :class:`CodeExecutionConfig` for declarative code execution on diff --git a/src/conductor/ai/agents/code_executor.py b/src/conductor/ai/agents/code_executor.py index aba0e7f7b..97ca851b7 100644 --- a/src/conductor/ai/agents/code_executor.py +++ b/src/conductor/ai/agents/code_executor.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Code executors — sandboxed environments for running LLM-generated code. Provides pre-built tools that let agents write and execute code safely. diff --git a/src/conductor/ai/agents/config_serializer.py b/src/conductor/ai/agents/config_serializer.py index ee68c0bac..fcc8ed67e 100644 --- a/src/conductor/ai/agents/config_serializer.py +++ b/src/conductor/ai/agents/config_serializer.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent → AgentConfig JSON serializer for server-side compilation. Converts a Python :class:`Agent` tree into a JSON-serializable dict diff --git a/src/conductor/ai/agents/exceptions.py b/src/conductor/ai/agents/exceptions.py index 05a55f502..4eea49ca5 100644 --- a/src/conductor/ai/agents/exceptions.py +++ b/src/conductor/ai/agents/exceptions.py @@ -1,11 +1,7 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Backward-compat shim — the exception hierarchy moved to ``conductor.client.ai``. Import from :mod:`conductor.client.ai.agent_errors` (or ``conductor.client.ai``) -going forward. This module re-exports the same objects, so existing -``except AgentspanError`` clauses and ``isinstance`` checks are unaffected. +going forward. This module re-exports the same objects. """ from __future__ import annotations @@ -13,6 +9,6 @@ from conductor.client.ai.agent_errors import ( # noqa: F401 AgentAPIError, AgentNotFoundError, - AgentspanError, + ConductorAgentError, _raise_api_error, ) diff --git a/src/conductor/ai/agents/ext.py b/src/conductor/ai/agents/ext.py index b479519f1..8ee85477b 100644 --- a/src/conductor/ai/agents/ext.py +++ b/src/conductor/ai/agents/ext.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Extended agent types — specialised agent classes for common patterns. - :class:`GPTAssistantAgent` — wraps the OpenAI Assistants API as an Agent. diff --git a/src/conductor/ai/agents/frameworks/__init__.py b/src/conductor/ai/agents/frameworks/__init__.py index 1eb161bec..7b3c8c660 100644 --- a/src/conductor/ai/agents/frameworks/__init__.py +++ b/src/conductor/ai/agents/frameworks/__init__.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Generic framework support for running foreign agents on the Conductor runtime. This package contains zero framework-specific code. It provides: diff --git a/src/conductor/ai/agents/frameworks/claude_agent_sdk.py b/src/conductor/ai/agents/frameworks/claude_agent_sdk.py index 6ae969186..643e93c5d 100644 --- a/src/conductor/ai/agents/frameworks/claude_agent_sdk.py +++ b/src/conductor/ai/agents/frameworks/claude_agent_sdk.py @@ -1,7 +1,4 @@ -# sdk/python/src/agentspan/agents/frameworks/claude_agent_sdk.py -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - +# sdk/python/src/conductor/ai/agents/frameworks/claude_agent_sdk.py """Claude Agent SDK passthrough worker support. Provides: @@ -285,10 +282,10 @@ def tool_worker(task: Task) -> TaskResult: from conductor.ai.agents.runtime.secret_injection import inject_via_env def _invoke(): - agentspan_hooks = _build_agentspan_hooks( + conductor_agent_hooks = _build_conductor_agent_hooks( task_id, execution_id, server_url, auth_key, auth_secret, metadata ) - merged_options = _merge_hooks(options, agentspan_hooks) + merged_options = _merge_hooks(options, conductor_agent_hooks) if cwd: if is_dataclass(merged_options) and not isinstance(merged_options, type): merged_options = replace(merged_options, cwd=cwd) @@ -376,11 +373,11 @@ async def _run_query(prompt: str, options: Any) -> Tuple[str, Any]: # --------------------------------------------------------------------------- -# Agentspan hooks +# Conductor-agent hooks # --------------------------------------------------------------------------- -def _build_agentspan_hooks( +def _build_conductor_agent_hooks( task_id: str, execution_id: str, server_url: str, @@ -388,7 +385,7 @@ def _build_agentspan_hooks( auth_secret: str, metadata: Dict[str, Any], ) -> Dict[str, list]: - """Build agentspan instrumentation hooks for the Claude Agent SDK. + """Build Conductor-agent instrumentation hooks for the Claude Agent SDK. Returns a dict mapping event names to lists of HookMatcher dataclasses. All hook callbacks are defensive (try/except, return {}). @@ -850,17 +847,17 @@ async def _stop(input_data: dict, tool_use_id: str | None, context: Any) -> dict # --------------------------------------------------------------------------- -def _merge_hooks(options: Any, agentspan_hooks: Dict[str, list]) -> Any: - """Merge user hooks and agentspan hooks, preserving user hooks first. +def _merge_hooks(options: Any, conductor_agent_hooks: Dict[str, list]) -> Any: + """Merge user hooks and Conductor-agent hooks, preserving user hooks first. Returns a new options object with the merged hooks dict. """ user_hooks = getattr(options, "hooks", None) or {} merged: Dict[str, list] = {} - all_events = set(list(user_hooks.keys()) + list(agentspan_hooks.keys())) + all_events = set(list(user_hooks.keys()) + list(conductor_agent_hooks.keys())) for event_name in all_events: user_matchers = user_hooks.get(event_name, []) - as_matchers = agentspan_hooks.get(event_name, []) + as_matchers = conductor_agent_hooks.get(event_name, []) merged[event_name] = list(user_matchers) + as_matchers # ClaudeCodeOptions is a dataclass -- use replace() @@ -968,7 +965,7 @@ def _create_tracking_workflow( """Create a bare tracking workflow for a subagent. Returns the execution ID of the new workflow, or None on failure. - Uses POST /api/agent/execution (Agentspan custom endpoint). + Uses POST /api/agent/execution (Conductor agent endpoint). """ body: Dict[str, Any] = {"workflowName": workflow_name, "input": input_data} if parent_workflow_id: @@ -995,7 +992,7 @@ def _inject_tool_task( """Inject a display-only task into the running workflow execution. Called synchronously from PreToolUse so the task exists before the tool runs. - Uses POST /api/agent/{executionId}/tasks (Agentspan custom endpoint). + Uses POST /api/agent/{executionId}/tasks (Conductor agent endpoint). For SUB_WORKFLOW tasks, pass sub_workflow_param with keys: name, version, executionId (the tracking sub-workflow). diff --git a/src/conductor/ai/agents/frameworks/langchain.py b/src/conductor/ai/agents/frameworks/langchain.py index d4fba09ff..65df212ee 100644 --- a/src/conductor/ai/agents/frameworks/langchain.py +++ b/src/conductor/ai/agents/frameworks/langchain.py @@ -1,7 +1,4 @@ -# sdk/python/src/agentspan/agents/frameworks/langchain.py -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - +# sdk/python/src/conductor/ai/agents/frameworks/langchain.py """LangChain AgentExecutor worker support — full extraction and passthrough.""" from __future__ import annotations @@ -171,8 +168,8 @@ def _get_callback_handler_class() -> type: from langchain_core.callbacks import BaseCallbackHandler - class AgentspanCallbackHandler(BaseCallbackHandler): - """LangChain callback handler that pushes events to Agentspan SSE via HTTP. + class ConductorAgentCallbackHandler(BaseCallbackHandler): + """LangChain callback handler that pushes events to Conductor-agent SSE via HTTP. Must inherit from BaseCallbackHandler so LangChain's AgentExecutor recognises it as a valid callback. Plain classes are rejected at runtime. @@ -230,7 +227,7 @@ def on_tool_error(self, error, **kwargs): self._auth_secret, ) - _callback_handler_class = AgentspanCallbackHandler + _callback_handler_class = ConductorAgentCallbackHandler return _callback_handler_class diff --git a/src/conductor/ai/agents/frameworks/langgraph.py b/src/conductor/ai/agents/frameworks/langgraph.py index 872152c5f..a7d6a274c 100644 --- a/src/conductor/ai/agents/frameworks/langgraph.py +++ b/src/conductor/ai/agents/frameworks/langgraph.py @@ -1,7 +1,4 @@ -# sdk/python/src/agentspan/agents/frameworks/langgraph.py -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - +# sdk/python/src/conductor/ai/agents/frameworks/langgraph.py """LangGraph worker support — full extraction, graph-structure, and passthrough. Provides: @@ -58,8 +55,8 @@ def review_email(state): """ def decorator(f): - f._agentspan_human_task = True - f._agentspan_human_prompt = prompt + f._conductor_agent_human_task = True + f._conductor_agent_human_prompt = prompt return f if func is not None: @@ -72,7 +69,7 @@ def serialize_langgraph(graph: Any) -> Tuple[Dict[str, Any], List[WorkerInfo]]: Tries three paths in order: 1. Full extraction (model + ToolNode tools) → AI_MODEL + SIMPLE per tool - Also used for create_agent graphs (detected via _agentspan_meta) + Also used for create_agent graphs (detected via _conductor_agent_meta) 2. Graph-structure (model found, custom StateGraph) → node/edge workflow 3. Passthrough (fallback) → single SIMPLE task """ @@ -227,8 +224,8 @@ def _serialize_graph_structure( worker_name = f"{name}_{node_name}" # Human node: no worker needed, compiled as Conductor HUMAN task - if getattr(func, "_agentspan_human_task", False): - human_prompt = getattr(func, "_agentspan_human_prompt", "") + if getattr(func, "_conductor_agent_human_task", False): + human_prompt = getattr(func, "_conductor_agent_human_prompt", "") logger.info("Human node '%s': will compile as Conductor HUMAN task", node_name) graph_nodes.append( { @@ -1666,7 +1663,7 @@ def _process_updates_chunk( auth_key: str, auth_secret: str, ) -> None: - """Map a LangGraph 'updates' chunk to Agentspan SSE events and push non-blocking.""" + """Map a LangGraph 'updates' chunk to Conductor-agent SSE events non-blockingly.""" for node_name, state_updates in chunk.items(): # Always emit a thinking event for each node execution _push_event_nonblocking( diff --git a/src/conductor/ai/agents/frameworks/serializer.py b/src/conductor/ai/agents/frameworks/serializer.py index 62c55cfc2..0e4283b3d 100644 --- a/src/conductor/ai/agents/frameworks/serializer.py +++ b/src/conductor/ai/agents/frameworks/serializer.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Generic agent serializer — zero framework-specific code. Provides: diff --git a/src/conductor/ai/agents/gate.py b/src/conductor/ai/agents/gate.py index f7c68764e..5e46bb95c 100644 --- a/src/conductor/ai/agents/gate.py +++ b/src/conductor/ai/agents/gate.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Gate conditions for conditional sequential pipelines.""" from __future__ import annotations diff --git a/src/conductor/ai/agents/guardrail.py b/src/conductor/ai/agents/guardrail.py index 54c9d298c..ef50e604c 100644 --- a/src/conductor/ai/agents/guardrail.py +++ b/src/conductor/ai/agents/guardrail.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Guardrails — input and output validation for agent responses. Guardrails compile to Conductor worker tasks positioned before (input) diff --git a/src/conductor/ai/agents/handoff.py b/src/conductor/ai/agents/handoff.py index 09b130fb6..f344111fc 100644 --- a/src/conductor/ai/agents/handoff.py +++ b/src/conductor/ai/agents/handoff.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Handoff conditions — rules that trigger automatic agent transitions. Used with ``strategy="swarm"`` to define post-tool and post-work diff --git a/src/conductor/ai/agents/langchain.py b/src/conductor/ai/agents/langchain.py index 82dd7c1dc..21c25602e 100644 --- a/src/conductor/ai/agents/langchain.py +++ b/src/conductor/ai/agents/langchain.py @@ -1,9 +1,6 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """User-facing create_agent wrapper for LangChain/LangGraph agents. -Import from here instead of langchain.agents so that Agentspan can +Import from here instead of langchain.agents so that Conductor can extract the LLM model, tools, and instructions for proper server-side orchestration (AI_MODEL + SIMPLE tasks — same as OpenAI/ADK). @@ -39,10 +36,10 @@ def create_agent( system_prompt: Optional[Union[str, Any]] = None, **kwargs: Any, ) -> Any: - """Agentspan wrapper around ``langchain.agents.create_agent``. + """Conductor wrapper around ``langchain.agents.create_agent``. Captures the LLM, tools, and instructions *before* compilation so that - Agentspan can translate the agent into a proper server-side workflow + Conductor can translate the agent into a proper server-side workflow (AI_MODEL task for the LLM + SIMPLE tasks for each tool), matching the OpenAI/ADK integration pattern. @@ -55,8 +52,8 @@ def create_agent( **kwargs: Forwarded to ``langchain.agents.create_agent``. Returns: - A ``CompiledStateGraph`` with ``._agentspan_meta`` attached for - proper Agentspan serialization. + A ``CompiledStateGraph`` with ``._conductor_agent_meta`` attached for + proper Conductor serialization. """ from langchain.agents import create_agent as _lc_create_agent # type: ignore[import] @@ -80,7 +77,7 @@ def create_agent( except AttributeError: pass - graph._agentspan_meta = { + graph._conductor_agent_meta = { "llm": model, "tools": resolved_tools, "instructions": instructions, diff --git a/src/conductor/ai/agents/memory.py b/src/conductor/ai/agents/memory.py index cd94ad551..508303646 100644 --- a/src/conductor/ai/agents/memory.py +++ b/src/conductor/ai/agents/memory.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Memory — session and conversation history management. Conversation state is persisted in Conductor workflow variables, surviving diff --git a/src/conductor/ai/agents/ocg.py b/src/conductor/ai/agents/ocg.py index ac46dd769..c80ce2685 100644 --- a/src/conductor/ai/agents/ocg.py +++ b/src/conductor/ai/agents/ocg.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OCG (Open Context Graph) retrieval sub-agent. OCG is a retrieval engine over a knowledge graph of entities (messages, diff --git a/src/conductor/ai/agents/openai_compat.py b/src/conductor/ai/agents/openai_compat.py index 2ef19129d..18b734300 100644 --- a/src/conductor/ai/agents/openai_compat.py +++ b/src/conductor/ai/agents/openai_compat.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenAI Agents SDK compatibility — drop-in Runner replacement. Change one import line:: @@ -11,8 +8,8 @@ # After from conductor.ai import Runner -Your agents now run on Agentspan instead of directly against OpenAI. -Agentspan adds durability, observability, human-in-the-loop, and horizontal +Your agents now run on Conductor instead of directly against OpenAI. +Conductor adds durability, observability, human-in-the-loop, and horizontal scaling — no other code changes needed. Compatible with openai-agents-python ``Agent``, ``@function_tool``, and @@ -76,7 +73,7 @@ def final_output(self) -> Any: @property def execution_id(self) -> str: - """The Agentspan execution ID for debugging.""" + """The Conductor execution ID for debugging.""" return self._agent_result.execution_id def __repr__(self) -> str: @@ -86,13 +83,13 @@ def __repr__(self) -> str: # ── Helpers ──────────────────────────────────────────────────────────────── -def _model_to_agentspan(model: Any) -> str: +def _model_to_conductor_agent(model: Any) -> str: """Add a provider prefix when the model name lacks one. ``"gpt-4o"`` → ``"openai/gpt-4o"`` ``"claude-opus-4-6"``→ ``"anthropic/claude-opus-4-6"`` ``"openai/gpt-4o"`` → ``"openai/gpt-4o"`` (already qualified) - ``None`` → ``""`` (Agentspan uses AGENTSPAN_LLM_MODEL env var) + ``None`` → ``""`` (the default model comes from configuration) """ if not model: return "" @@ -136,7 +133,7 @@ class _CtxStub: openai-agents' FunctionTool.on_invoke_tool(ctx, json_str) accesses ctx attributes for tracing/span creation. When executing inside an - Agentspan worker (outside of an openai-agents Runner loop) there is + Conductor worker (outside of an openai-agents Runner loop) there is no real RunContextWrapper, so we supply a stub that silently returns None for any attribute access rather than raising AttributeError. """ @@ -149,14 +146,14 @@ def __getattr__(self, name: str) -> Any: def _convert_function_tool(ft: Any) -> Any: - """Convert an openai-agents ``FunctionTool`` to an Agentspan ``ToolDef``. + """Convert an openai-agents ``FunctionTool`` to a Conductor ``ToolDef``. Args: ft: Object with ``.name``, ``.description``, ``.params_json_schema``, and ``.on_invoke_tool(ctx, input_json_str)`` attributes. Returns: - An Agentspan :class:`~conductor.ai.agents.tool.ToolDef`. + A Conductor :class:`~conductor.ai.agents.tool.ToolDef`. """ from conductor.ai.agents.tool import ToolDef @@ -184,10 +181,10 @@ def _sync_wrapper(**kwargs: Any) -> Any: ) -def _to_agentspan_agent(agent: Any) -> Any: - """Convert an openai-agents ``Agent`` to an Agentspan ``Agent``. +def _to_conductor_agent_agent(agent: Any) -> Any: + """Convert an openai-agents ``Agent`` to a Conductor ``Agent``. - If *agent* is already an Agentspan ``Agent`` it is returned unchanged. + If *agent* is already a Conductor ``Agent`` it is returned unchanged. Duck-typed: any object with ``name``, ``instructions``, ``model``, and ``tools`` attributes is accepted. """ @@ -210,22 +207,26 @@ def _to_agentspan_agent(agent: Any) -> Any: _raw_model = getattr(agent, "model", None) model: str = ( - _model_to_agentspan(_raw_model) + _model_to_conductor_agent(_raw_model) if _raw_model - else (os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o")) + else ( + os.environ.get("CONDUCTOR_AGENT_LLM_MODEL") + or os.environ.get("CONDUCTOR_AGENT_LLM_MODEL") + or "openai/gpt-4o" + ) ) raw_tools: list = getattr(agent, "tools", []) or [] - agentspan_tools = [] + conductor_agent_tools = [] for t in raw_tools: if hasattr(t, "on_invoke_tool"): - agentspan_tools.append(_convert_function_tool(t)) + conductor_agent_tools.append(_convert_function_tool(t)) elif hasattr(t, "_tool_def"): - agentspan_tools.append(t) + conductor_agent_tools.append(t) else: logger.warning( "Skipping unrecognised tool type '%s' — " - "wrap it with Agentspan's @tool decorator to include it.", + "wrap it with Conductor's @tool decorator to include it.", type(t).__name__, ) @@ -233,7 +234,7 @@ def _to_agentspan_agent(agent: Any) -> Any: name=name, instructions=instructions, model=model, - tools=agentspan_tools, + tools=conductor_agent_tools, ) @@ -241,18 +242,18 @@ def _to_agentspan_agent(agent: Any) -> Any: def _run_agent(starting_agent: Any, max_turns: int) -> Any: - """Resolve the agent to pass to the Agentspan runtime. + """Resolve the agent to pass to the Conductor runtime. Foreign framework agents (openai-agents, google-adk, …) are passed through unchanged — the runtime's :func:`detect_framework` handles - serialisation and tool registration. Native Agentspan Agents are also + serialization and tool registration. Native Conductor Agents are also passed through unchanged (with optional ``max_turns`` override). Only - truly unknown objects fall back to :func:`_to_agentspan_agent`. + truly unknown objects fall back to :func:`_to_conductor_agent_agent`. """ - from conductor.ai.agents.agent import Agent as AgentspanAgent + from conductor.ai.agents.agent import Agent as ConductorAgent from conductor.ai.agents.frameworks.serializer import detect_framework - if isinstance(starting_agent, AgentspanAgent): + if isinstance(starting_agent, ConductorAgent): if max_turns != 10: starting_agent.max_turns = max_turns return starting_agent @@ -265,7 +266,7 @@ def _run_agent(starting_agent: Any, max_turns: int) -> Any: return starting_agent # Unknown type — attempt duck-type conversion - agent = _to_agentspan_agent(starting_agent) + agent = _to_conductor_agent_agent(starting_agent) if max_turns != 10: agent.max_turns = max_turns return agent @@ -290,7 +291,7 @@ class Runner: ``await Runner.run(agent, input)`` Async execution. Returns a :class:`RunResult`. ``Runner.run_streamed(agent, input)`` - Streaming execution. Returns an Agentspan :class:`AsyncAgentStream`. + Streaming execution. Returns a Conductor :class:`AsyncAgentStream`. """ @classmethod @@ -308,7 +309,7 @@ def run_sync( Drop-in for ``Runner.run_sync(agent, input)``. Args: - starting_agent: An openai-agents ``Agent`` or Agentspan ``Agent``. + starting_agent: An openai-agents ``Agent`` or Conductor ``Agent``. input: The user's input message. context: Ignored — present only for openai-agents API compatibility. max_turns: Maximum agent loop iterations. @@ -317,10 +318,10 @@ def run_sync( Returns: A :class:`RunResult` with a ``final_output`` attribute. """ - from conductor.ai.agents.run import run as agentspan_run + from conductor.ai.agents.run import run as conductor_agent_run agent = _run_agent(starting_agent, max_turns) - result = agentspan_run(agent, input) + result = conductor_agent_run(agent, input) return RunResult(result) @classmethod @@ -338,7 +339,7 @@ async def run( Drop-in for ``await Runner.run(agent, input)``. Args: - starting_agent: An openai-agents ``Agent`` or Agentspan ``Agent``. + starting_agent: An openai-agents ``Agent`` or Conductor ``Agent``. input: The user's input message. context: Ignored — present only for openai-agents API compatibility. max_turns: Maximum agent loop iterations. @@ -367,15 +368,15 @@ async def run_streamed( Drop-in for ``Runner.run_streamed(agent, input)``. - Returns an Agentspan :class:`~conductor.ai.agents.result.AsyncAgentStream` + Returns a Conductor :class:`~conductor.ai.agents.result.AsyncAgentStream` which supports ``async for event in stream`` iteration. - Note: Agentspan event types (``"tool_call"``, ``"done"``, etc.) differ + Note: Conductor event types (``"tool_call"``, ``"done"``, etc.) differ from openai-agents ``StreamEvent`` types. For full streaming event compatibility, iterate and map events as needed. Args: - starting_agent: An openai-agents ``Agent`` or Agentspan ``Agent``. + starting_agent: An openai-agents ``Agent`` or Conductor ``Agent``. input: The user's input message. context: Ignored — present only for openai-agents API compatibility. max_turns: Maximum agent loop iterations. diff --git a/src/conductor/ai/agents/plans.py b/src/conductor/ai/agents/plans.py index 77fedc959..7e3748863 100644 --- a/src/conductor/ai/agents/plans.py +++ b/src/conductor/ai/agents/plans.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Typed plan builders for ``Strategy.PLAN_EXECUTE``. These dataclasses produce the JSON shape PAC (the server's PLAN_AND_COMPILE diff --git a/src/conductor/ai/agents/result.py b/src/conductor/ai/agents/result.py index fb28cf455..a06930bdd 100644 --- a/src/conductor/ai/agents/result.py +++ b/src/conductor/ai/agents/result.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Result types — AgentResult, AgentHandle, AgentEvent, AgentStatus, AgentStream, AsyncAgentStream. These classes provide the interface between the user and a running or diff --git a/src/conductor/ai/agents/run.py b/src/conductor/ai/agents/run.py index 8763f9c33..d6a636551 100644 --- a/src/conductor/ai/agents/run.py +++ b/src/conductor/ai/agents/run.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Convenience execution API — run(), start(), stream(), run_async(), start_async(), stream_async(). These top-level functions provide a quick way to execute agents using a diff --git a/src/conductor/ai/agents/run_settings.py b/src/conductor/ai/agents/run_settings.py index 5347135e7..635fcecde 100644 --- a/src/conductor/ai/agents/run_settings.py +++ b/src/conductor/ai/agents/run_settings.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Per-run LLM overrides for :meth:`AgentRuntime.run` / :meth:`AgentRuntime.start`. ``RunSettings`` lets a single invocation override the LLM settings baked into an diff --git a/src/conductor/ai/agents/runtime/__init__.py b/src/conductor/ai/agents/runtime/__init__.py index 27dc233b4..3596e8589 100644 --- a/src/conductor/ai/agents/runtime/__init__.py +++ b/src/conductor/ai/agents/runtime/__init__.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Runtime package — execution lifecycle management.""" from __future__ import annotations diff --git a/src/conductor/ai/agents/runtime/_dispatch.py b/src/conductor/ai/agents/runtime/_dispatch.py index 988c77e64..647e60ad7 100644 --- a/src/conductor/ai/agents/runtime/_dispatch.py +++ b/src/conductor/ai/agents/runtime/_dispatch.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tool execution workers for native function calling. This file deliberately does NOT use ``from __future__ import annotations`` @@ -40,7 +37,7 @@ def _validate_serializable(tool_name, result): def _is_framework_callable(tool_func) -> bool: - return bool(getattr(tool_func, "_agentspan_framework_callable", False)) + return bool(getattr(tool_func, "_conductor_agent_framework_callable", False)) def _to_namespace(value): @@ -159,7 +156,7 @@ def _coerce_value(value, annotation): def _resolve_secrets_from_task(task, names: list) -> dict: """Resolve declared secret *names* from the values the host delivered on the task. - Secured hosts (e.g. orkes/agentspan) resolve a worker's declared + Secured Conductor hosts resolve a worker's declared ``TaskDef.runtimeMetadata`` secret names at poll time and deliver the values on the wire-only ``Task.runtimeMetadata`` map (conductor-oss PR #1255) — never persisted to task input. The worker just reads them here; there is no separate @@ -179,7 +176,7 @@ def _resolve_secrets_from_task(task, names: list) -> dict: raise CredentialNotFoundError( missing, "Not delivered by the server on this task. Ensure each secret is stored " - "(agentspan credentials set --name <NAME>) and declared on the tool/agent " + "in the Conductor credential store and declared on the tool/agent " "so the server resolves it at poll time.", ) return resolved @@ -319,7 +316,7 @@ def run_tool_task(task, *, tool_name, tool_func, guardrails=None, credential_nam # Re-apply the parent-side marker: a spawn child's re-imported # function copy does not carry attributes set in the parent. try: - tool_func._agentspan_framework_callable = True + tool_func._conductor_agent_framework_callable = True except Exception: pass _closure_cred_names = list(credential_names) if credential_names else [] diff --git a/src/conductor/ai/agents/runtime/_liveness.py b/src/conductor/ai/agents/runtime/_liveness.py index c36271fce..186236a8c 100644 --- a/src/conductor/ai/agents/runtime/_liveness.py +++ b/src/conductor/ai/agents/runtime/_liveness.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Worker liveness verification + stall detection. Two complementary mechanisms protect against the "pollCount=0" failure @@ -155,7 +152,7 @@ def verify( "The worker subprocess(es) are not running. This usually means " "fork() failed or an exception was swallowed during " "WorkerManager.start(). Check process logs and retry start(). " - "Set AGENTSPAN_LIVENESS_ENABLED=false to disable this check." + "Set CONDUCTOR_AGENT_LIVENESS_ENABLED=false to disable this check." ), ) @@ -267,7 +264,7 @@ def _tick(self) -> bool: "No worker is polling for these tasks. If the original " "process died, re-run with the same idempotency_key (or " "call runtime.resume(execution_id, agent)) to re-attach " - "workers. Set AGENTSPAN_LIVENESS_ENABLED=false to disable." + "workers. Set CONDUCTOR_AGENT_LIVENESS_ENABLED=false to disable." ), ) try: diff --git a/src/conductor/ai/agents/runtime/_worker_entries.py b/src/conductor/ai/agents/runtime/_worker_entries.py index 9373f13f2..f6f1a92eb 100644 --- a/src/conductor/ai/agents/runtime/_worker_entries.py +++ b/src/conductor/ai/agents/runtime/_worker_entries.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Spawn-safe worker entry infrastructure. Under the multiprocessing ``spawn`` start method (the SDK default on every @@ -23,7 +20,7 @@ - :func:`probe_spawn_safety` — a registration-time pickle probe, enabled per worker group as each group's workers become spawn-safe. -Design: idea-5 ``design.md`` in the combine-agentspan analysis workspace. +Design: idea-5 ``design.md`` in the agent-runtime analysis workspace. NOTE: deliberately NO ``from __future__ import annotations`` here — the task runners read parameter types from ``inspect.signature(execute_function)`` @@ -205,7 +202,7 @@ def __init__(self, tool_name, fn_ref=None, fn_direct=None, guardrails=None, self.fn_direct = fn_direct self.guardrails = guardrails self.credential_names = list(credential_names) if credential_names else [] - # Carried explicitly: the parent sets _agentspan_framework_callable on + # Carried explicitly: the parent sets _conductor_agent_framework_callable on # the function object, which a spawn child's re-imported copy lacks. self.framework_callable = framework_callable @@ -217,7 +214,7 @@ def for_callable(cls, fn, tool_name, guardrails=None, credential_names=None): bound methods of picklable objects); unpicklable closures are caught by the registration probe with an actionable error. """ - framework = bool(getattr(fn, "_agentspan_framework_callable", False)) + framework = bool(getattr(fn, "_conductor_agent_framework_callable", False)) try: ref = FunctionRef.of(fn) except SpawnSafetyError: diff --git a/src/conductor/ai/agents/runtime/config.py b/src/conductor/ai/agents/runtime/config.py index 9a52b00b7..1484de8bf 100644 --- a/src/conductor/ai/agents/runtime/config.py +++ b/src/conductor/ai/agents/runtime/config.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Configuration — load settings from environment variables. Uses ``dataclasses`` with a ``from_env()`` classmethod for env var loading. @@ -17,39 +14,50 @@ import logging import os from dataclasses import dataclass -from typing import Optional - - def _env(var: str, default=None): - """Read an environment variable, returning *default* if unset.""" - return os.environ.get(var, default) + """Read an environment variable, treating blank values as unset.""" + value = os.environ.get(var) + if value is not None and value.strip() != "": + return value + return default logger = logging.getLogger("conductor.ai.agents.config") def _env_bool(var: str, default: bool = False) -> bool: - """Read a boolean environment variable (true/1/yes → True).""" - val = os.environ.get(var) - if val is None or val.strip() == "": + """Read a boolean environment variable; malformed values use *default*.""" + val = _env(var) + if val is None: return default - return val.lower() in ("true", "1", "yes") + normalized = val.lower() + if normalized in ("true", "1", "yes", "on"): + return True + if normalized in ("false", "0", "no", "off"): + return False + return default def _env_int(var: str, default: int = 0) -> int: - """Read an integer environment variable.""" - val = os.environ.get(var) - if val is None or val.strip() == "": + """Read an integer environment variable; malformed values use *default*.""" + val = _env(var) + if val is None: + return default + try: + return int(val) + except ValueError: return default - return int(val) def _env_float(var: str, default: float = 0.0) -> float: - """Read a float environment variable.""" - val = os.environ.get(var) - if val is None or val.strip() == "": + """Read a float environment variable; malformed values use *default*.""" + val = _env(var) + if val is None: + return default + try: + return float(val) + except ValueError: return default - return float(val) @dataclass @@ -86,17 +94,18 @@ class AgentConfig: @classmethod def from_env(cls) -> AgentConfig: - """Create an ``AgentConfig`` by reading ``AGENTSPAN_*`` env vars.""" + """Create an ``AgentConfig`` from canonical environment variables. + + Only ``CONDUCTOR_AGENT_*`` settings are supported. + """ return cls( - worker_poll_interval_ms=_env_int("AGENTSPAN_WORKER_POLL_INTERVAL", 100), - worker_thread_count=_env_int("AGENTSPAN_WORKER_THREADS", 1), - auto_start_workers=_env_bool("AGENTSPAN_AUTO_START_WORKERS", True), - daemon_workers=_env_bool("AGENTSPAN_DAEMON_WORKERS", True), - auto_register_integrations=_env_bool("AGENTSPAN_INTEGRATIONS_AUTO_REGISTER", False), - streaming_enabled=_env_bool("AGENTSPAN_STREAMING_ENABLED", True), - liveness_enabled=_env_bool("AGENTSPAN_LIVENESS_ENABLED", True), - liveness_stall_seconds=_env_float("AGENTSPAN_LIVENESS_STALL_SECONDS", 30.0), - liveness_check_interval_seconds=_env_float( - "AGENTSPAN_LIVENESS_CHECK_INTERVAL_SECONDS", 10.0 - ), + worker_poll_interval_ms=_env_int("CONDUCTOR_AGENT_WORKER_POLL_INTERVAL", 100), + worker_thread_count=_env_int("CONDUCTOR_AGENT_WORKER_THREADS", 1), + auto_start_workers=_env_bool("CONDUCTOR_AGENT_AUTO_START_WORKERS", True), + daemon_workers=_env_bool("CONDUCTOR_AGENT_DAEMON_WORKERS", True), + auto_register_integrations=_env_bool("CONDUCTOR_AGENT_INTEGRATIONS_AUTO_REGISTER", False), + streaming_enabled=_env_bool("CONDUCTOR_AGENT_STREAMING_ENABLED", True), + liveness_enabled=_env_bool("CONDUCTOR_AGENT_LIVENESS_ENABLED", True), + liveness_stall_seconds=_env_float("CONDUCTOR_AGENT_LIVENESS_STALL_SECONDS", 30.0), + liveness_check_interval_seconds=_env_float("CONDUCTOR_AGENT_LIVENESS_CHECK_INTERVAL_SECONDS", 10.0), ) diff --git a/src/conductor/ai/agents/runtime/credentials/__init__.py b/src/conductor/ai/agents/runtime/credentials/__init__.py index ab69580a1..5466df8e7 100644 --- a/src/conductor/ai/agents/runtime/credentials/__init__.py +++ b/src/conductor/ai/agents/runtime/credentials/__init__.py @@ -1,7 +1,4 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - -"""Credential management subpackage for the Agentspan Python SDK.""" +"""Credential management for the Conductor Python SDK.""" from __future__ import annotations diff --git a/src/conductor/ai/agents/runtime/credentials/accessor.py b/src/conductor/ai/agents/runtime/credentials/accessor.py index 1e9f0b7eb..3ff083649 100644 --- a/src/conductor/ai/agents/runtime/credentials/accessor.py +++ b/src/conductor/ai/agents/runtime/credentials/accessor.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """get_secret() accessor — read resolved credentials without going through env vars. The worker framework calls ``set_credential_context(secrets_dict)`` before diff --git a/src/conductor/ai/agents/runtime/credentials/types.py b/src/conductor/ai/agents/runtime/credentials/types.py index 79d536e0f..8855a9c87 100644 --- a/src/conductor/ai/agents/runtime/credentials/types.py +++ b/src/conductor/ai/agents/runtime/credentials/types.py @@ -1,21 +1,18 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Credential exception hierarchy.""" from __future__ import annotations from typing import List -from conductor.ai.agents.exceptions import AgentspanError +from conductor.ai.agents.exceptions import ConductorAgentError -class CredentialNotFoundError(AgentspanError): +class CredentialNotFoundError(ConductorAgentError): """One or more required credentials could not be resolved. Raised when a declared credential is not found in the credential store. There is no env var fallback for declared credentials — store them with - ``agentspan credentials set --name <NAME>``. + Configure the credential in the Conductor server's secret provider. """ def __init__(self, missing_names: List[str], detail: str = "") -> None: @@ -27,7 +24,7 @@ def __init__(self, missing_names: List[str], detail: str = "") -> None: super().__init__(msg) -class CredentialAuthError(AgentspanError): +class CredentialAuthError(ConductorAgentError): """Execution token is invalid, expired, or revoked. Raised on HTTP 401 from ``/api/workers/credentials``. @@ -41,7 +38,7 @@ def __init__(self, detail: str = "") -> None: super().__init__(msg) -class CredentialRateLimitError(AgentspanError): +class CredentialRateLimitError(ConductorAgentError): """Rate limit exceeded on ``/api/workers/credentials`` (HTTP 429). Do NOT fall through to env var fallback. @@ -54,7 +51,7 @@ def __init__(self) -> None: ) -class CredentialServiceError(AgentspanError): +class CredentialServiceError(ConductorAgentError): """Credential service returned a 5xx error or is unreachable. Always fatal — no env var fallback. diff --git a/src/conductor/ai/agents/runtime/discovery.py b/src/conductor/ai/agents/runtime/discovery.py index 08169821e..3b763405d 100644 --- a/src/conductor/ai/agents/runtime/discovery.py +++ b/src/conductor/ai/agents/runtime/discovery.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent discovery -- scan Python packages for Agent instances.""" from __future__ import annotations diff --git a/src/conductor/ai/agents/runtime/mcp_discovery.py b/src/conductor/ai/agents/runtime/mcp_discovery.py index 5289d8255..037c59958 100644 --- a/src/conductor/ai/agents/runtime/mcp_discovery.py +++ b/src/conductor/ai/agents/runtime/mcp_discovery.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """MCP tool discovery — discovers individual tools from MCP servers at compile time. Uses Conductor's ``LIST_MCP_TOOLS`` system task to query an MCP server and diff --git a/src/conductor/ai/agents/runtime/runtime.py b/src/conductor/ai/agents/runtime/runtime.py index 0c54363e2..5b7c2c3aa 100644 --- a/src/conductor/ai/agents/runtime/runtime.py +++ b/src/conductor/ai/agents/runtime/runtime.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """AgentRuntime — the execution engine for running agents on Conductor. This is the core orchestrator that: @@ -183,7 +180,7 @@ def _resolve_loop_iteration(iteration: object) -> int: The compiler wires a worker's ``iteration`` input from the loop counter reference ``${<agent>_loop.iteration}``. Conductor cores disagree on whether that loop-output reference resolves for tasks executing *inside* the loop - body: the OSS core agentspan compiles against resolves it to the live integer, + body: the OSS core agent compiler resolves it to the live integer, but some embedding hosts' cores (e.g. orkes-conductor) leave it unresolved and deliver ``None`` — which then crashes ``iteration >= max_retries`` comparisons. @@ -348,7 +345,7 @@ class AgentRuntime: Like every other client, server connection comes from the standard :class:`~conductor.client.configuration.configuration.Configuration` - (``CONDUCTOR_SERVER_URL`` → ``AGENTSPAN_SERVER_URL``, ``CONDUCTOR_AUTH_KEY``/ + (``CONDUCTOR_SERVER_URL`` → ``CONDUCTOR_SERVER_URL``, ``CONDUCTOR_AUTH_KEY``/ ``CONDUCTOR_AUTH_SECRET`` when not passed explicitly):: from conductor.client.configuration.configuration import Configuration @@ -575,8 +572,6 @@ def _start_via_server( payload["idempotencyKey"] = idempotency_key if timeout is not None: payload["timeoutSeconds"] = timeout - if credentials: - payload["credentials"] = credentials if run_id: payload["runId"] = run_id if static_plan is not None: @@ -639,8 +634,6 @@ async def _start_via_server_async( payload["idempotencyKey"] = idempotency_key if timeout is not None: payload["timeoutSeconds"] = timeout - if credentials: - payload["credentials"] = credentials if run_id: payload["runId"] = run_id @@ -682,8 +675,6 @@ async def _start_framework_via_server_async( } if idempotency_key: payload["idempotencyKey"] = idempotency_key - if credentials: - payload["credentials"] = credentials data = await self._agent_client.start_agent_async(payload) execution_id = data.get("executionId", "") @@ -2988,8 +2979,6 @@ def _start_framework_via_server( } if idempotency_key: payload["idempotencyKey"] = idempotency_key - if credentials: - payload["credentials"] = credentials data = self._agent_client.start_agent(payload) execution_id = data.get("executionId", "") @@ -3013,7 +3002,7 @@ def _register_framework_workers( for w in workers: try: - setattr(w.func, "_agentspan_framework_callable", True) + setattr(w.func, "_conductor_agent_framework_callable", True) except Exception: pass wrapper = make_tool_worker(w.func, w.name, credential_names=credentials) @@ -3739,7 +3728,7 @@ def _stream_polling(self, execution_id: str) -> Iterator[AgentEvent]: fn_name = task_type.lower() raw_args = getattr(task, "input_data", None) or {} clean_args = { - k: v for k, v in raw_args.items() if k != "__agentspan_ctx__" + k: v for k, v in raw_args.items() if k != "__conductor_agent_ctx__" } yield AgentEvent( type=EventType.TOOL_CALL, @@ -4268,7 +4257,7 @@ async def _stream_polling_async(self, execution_id: str) -> AsyncIterator[AgentE fn_name = task_type.lower() raw_args = getattr(task, "input_data", None) or {} clean_args = { - k: v for k, v in raw_args.items() if k != "__agentspan_ctx__" + k: v for k, v in raw_args.items() if k != "__conductor_agent_ctx__" } yield AgentEvent( type=EventType.TOOL_CALL, diff --git a/src/conductor/ai/agents/runtime/tool_registry.py b/src/conductor/ai/agents/runtime/tool_registry.py index 0ae4e4189..595e26732 100644 --- a/src/conductor/ai/agents/runtime/tool_registry.py +++ b/src/conductor/ai/agents/runtime/tool_registry.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tool registry — registers @tool functions as Conductor workers for polling.""" from __future__ import annotations diff --git a/src/conductor/ai/agents/runtime/worker_manager.py b/src/conductor/ai/agents/runtime/worker_manager.py index 651ab351c..e12162b3b 100644 --- a/src/conductor/ai/agents/runtime/worker_manager.py +++ b/src/conductor/ai/agents/runtime/worker_manager.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Worker manager — auto-registers @tool functions as Conductor workers. Bridges ``@tool``-decorated Python functions to Conductor's diff --git a/src/conductor/ai/agents/schedule/__init__.py b/src/conductor/ai/agents/schedule/__init__.py index 58e12292a..ea2f7b96a 100644 --- a/src/conductor/ai/agents/schedule/__init__.py +++ b/src/conductor/ai/agents/schedule/__init__.py @@ -1,6 +1,3 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Cron-based scheduling for deployed agents. A :class:`Schedule` attaches a cron trigger to an agent at deploy time. diff --git a/src/conductor/ai/agents/schedule/api.py b/src/conductor/ai/agents/schedule/api.py index dd2a0d02e..62a8d1758 100644 --- a/src/conductor/ai/agents/schedule/api.py +++ b/src/conductor/ai/agents/schedule/api.py @@ -1,6 +1,3 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Module-level lifecycle API for schedules. Lifecycle calls are keyed by the **wire name** (the prefixed identifier diff --git a/src/conductor/ai/agents/schedule/errors.py b/src/conductor/ai/agents/schedule/errors.py index 2a55e3cac..75604bde0 100644 --- a/src/conductor/ai/agents/schedule/errors.py +++ b/src/conductor/ai/agents/schedule/errors.py @@ -1,6 +1,3 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Backward-compat shim — schedule exceptions moved to ``conductor.client.ai``. Import from :mod:`conductor.client.ai.schedule_errors` (or ``conductor.client.ai``) diff --git a/src/conductor/ai/agents/schedule/schedule.py b/src/conductor/ai/agents/schedule/schedule.py index 7c199221e..8a349d4ba 100644 --- a/src/conductor/ai/agents/schedule/schedule.py +++ b/src/conductor/ai/agents/schedule/schedule.py @@ -1,6 +1,3 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Backward-compat shim — ``Schedule``/``ScheduleInfo`` moved to ``conductor.client.ai``. Import from :mod:`conductor.client.ai` going forward. This module re-exports the diff --git a/src/conductor/ai/agents/semantic_memory.py b/src/conductor/ai/agents/semantic_memory.py index 0b994976a..782562f03 100644 --- a/src/conductor/ai/agents/semantic_memory.py +++ b/src/conductor/ai/agents/semantic_memory.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Semantic memory — long-term memory with similarity-based retrieval. Provides cross-session memory for agents, enabling them to recall diff --git a/src/conductor/ai/agents/skill.py b/src/conductor/ai/agents/skill.py index 74426f537..029ba2369 100644 --- a/src/conductor/ai/agents/skill.py +++ b/src/conductor/ai/agents/skill.py @@ -145,7 +145,7 @@ def skill( search_path: Optional[List[str]] = None, params: Optional[Dict[str, Any]] = None, ) -> Agent: - """Load an Agent Skills directory as an Agentspan Agent. + """Load an Agent Skills directory as a Conductor agent. Args: path: Path to skill directory containing SKILL.md. diff --git a/src/conductor/ai/agents/termination.py b/src/conductor/ai/agents/termination.py index e0287de9a..0f3d95e47 100644 --- a/src/conductor/ai/agents/termination.py +++ b/src/conductor/ai/agents/termination.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Termination conditions — composable rules that decide when an agent should stop. Conditions can be combined with ``&`` (AND — all must trigger) and ``|`` diff --git a/src/conductor/ai/agents/testing/__init__.py b/src/conductor/ai/agents/testing/__init__.py index 2189465a9..cd4dffaa4 100644 --- a/src/conductor/ai/agents/testing/__init__.py +++ b/src/conductor/ai/agents/testing/__init__.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent correctness testing framework. Provides composable assertions, mock execution, fluent API, and diff --git a/src/conductor/ai/agents/testing/assertions.py b/src/conductor/ai/agents/testing/assertions.py index c7144a3c0..ba338a5ee 100644 --- a/src/conductor/ai/agents/testing/assertions.py +++ b/src/conductor/ai/agents/testing/assertions.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Composable assertion functions for agent correctness testing. Every function takes an :class:`AgentResult` as its first argument and raises diff --git a/src/conductor/ai/agents/testing/eval_runner.py b/src/conductor/ai/agents/testing/eval_runner.py index c30a4d6b6..6d649e841 100644 --- a/src/conductor/ai/agents/testing/eval_runner.py +++ b/src/conductor/ai/agents/testing/eval_runner.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Eval runner — LLM-backed correctness testing for agent behavior. Runs real prompts through agents and evaluates whether the agent's behavior @@ -187,7 +184,7 @@ def failed_cases(self) -> List[EvalCaseResult]: class CorrectnessEval: - """Runs eval cases against a live Agentspan runtime. + """Runs eval cases against a live Conductor-agent runtime. Args: runtime: An :class:`AgentRuntime` instance (or any object with a diff --git a/src/conductor/ai/agents/testing/expect.py b/src/conductor/ai/agents/testing/expect.py index bbb5c5057..b2b164fb5 100644 --- a/src/conductor/ai/agents/testing/expect.py +++ b/src/conductor/ai/agents/testing/expect.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Fluent assertion API for agent correctness testing. Usage:: diff --git a/src/conductor/ai/agents/testing/mock.py b/src/conductor/ai/agents/testing/mock.py index 6321f3ece..e4fd322b1 100644 --- a/src/conductor/ai/agents/testing/mock.py +++ b/src/conductor/ai/agents/testing/mock.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Mock execution — deterministic agent testing without LLM or server. Provides :class:`MockEvent` (a factory for :class:`AgentEvent` objects) and diff --git a/src/conductor/ai/agents/testing/pytest_plugin.py b/src/conductor/ai/agents/testing/pytest_plugin.py index 01ab5a35f..959f9cd19 100644 --- a/src/conductor/ai/agents/testing/pytest_plugin.py +++ b/src/conductor/ai/agents/testing/pytest_plugin.py @@ -1,10 +1,7 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Pytest plugin — fixtures and markers for agent correctness testing. Registered automatically via the ``pytest11`` entry point when -``agentspan`` is installed. +the optional Conductor-agent dependencies are installed. """ from __future__ import annotations diff --git a/src/conductor/ai/agents/testing/recording.py b/src/conductor/ai/agents/testing/recording.py index 147175cbf..1212fc354 100644 --- a/src/conductor/ai/agents/testing/recording.py +++ b/src/conductor/ai/agents/testing/recording.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Record and replay agent execution traces for regression testing. Usage:: diff --git a/src/conductor/ai/agents/testing/semantic.py b/src/conductor/ai/agents/testing/semantic.py index f2dfe6a69..80dcabfcf 100644 --- a/src/conductor/ai/agents/testing/semantic.py +++ b/src/conductor/ai/agents/testing/semantic.py @@ -1,11 +1,8 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Semantic assertions using an LLM judge. Requires ``litellm`` (optional dependency). Install with:: - pip install agentspan[testing] + pip install conductor-python[agents] Usage:: @@ -52,7 +49,7 @@ def assert_output_satisfies( except ImportError: raise ImportError( "litellm is required for semantic assertions.\n" - "Install it with: pip install agentspan[testing]" + "Install it with: pip install conductor-python[agents]" ) output = str(result.output) if result.output is not None else "" diff --git a/src/conductor/ai/agents/testing/strategy_validators.py b/src/conductor/ai/agents/testing/strategy_validators.py index 641f2bb9c..395f1eef8 100644 --- a/src/conductor/ai/agents/testing/strategy_validators.py +++ b/src/conductor/ai/agents/testing/strategy_validators.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Strategy validators — verify that an execution trace obeys the rules of its strategy. These validators inspect an :class:`AgentResult` and the :class:`Agent` definition diff --git a/src/conductor/ai/agents/tool.py b/src/conductor/ai/agents/tool.py index cde9565f2..e06a70f9a 100644 --- a/src/conductor/ai/agents/tool.py +++ b/src/conductor/ai/agents/tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tool definitions — @tool decorator and server-side tool constructors. Tools are **server-first**: ``@tool`` functions become Conductor task definitions diff --git a/src/conductor/ai/agents/tracing.py b/src/conductor/ai/agents/tracing.py index 8a3c797a5..051e6aa18 100644 --- a/src/conductor/ai/agents/tracing.py +++ b/src/conductor/ai/agents/tracing.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """OpenTelemetry tracing — industry-standard observability for agent execution. Automatically instruments agent runs with spans for LLM calls, tool diff --git a/src/conductor/client/agent_client.py b/src/conductor/client/agent_client.py index ac19ac958..186ae594a 100644 --- a/src/conductor/client/agent_client.py +++ b/src/conductor/client/agent_client.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Abstract interface for the Agent Runtime API (``/agent/*`` control plane). Follows the same interface + Orkes-implementation convention as diff --git a/src/conductor/client/ai/__init__.py b/src/conductor/client/ai/__init__.py index b917b4b8b..58ea36225 100644 --- a/src/conductor/client/ai/__init__.py +++ b/src/conductor/client/ai/__init__.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Agent-facing models and exceptions for the Conductor SDK. Home of the agent schedule models (:class:`Schedule`/:class:`ScheduleInfo`) and @@ -19,7 +16,7 @@ from conductor.client.ai.agent_errors import ( AgentAPIError, AgentNotFoundError, - AgentspanError, + ConductorAgentError, ) from conductor.client.ai.schedule import Schedule, ScheduleInfo from conductor.client.ai.schedule_errors import ( @@ -33,7 +30,7 @@ "AgentClient", "AgentAPIError", "AgentNotFoundError", - "AgentspanError", + "ConductorAgentError", "InvalidCronExpression", "Schedule", "ScheduleError", diff --git a/src/conductor/client/ai/agent_errors.py b/src/conductor/client/ai/agent_errors.py index 9a2d43512..22139ccae 100644 --- a/src/conductor/client/ai/agent_errors.py +++ b/src/conductor/client/ai/agent_errors.py @@ -1,24 +1,20 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """SDK-level exceptions for the agent API clients. Provides a consistent exception hierarchy so users don't need to catch library-specific HTTP errors from ``requests`` or ``httpx``. -This module is the canonical home of the hierarchy formerly defined in -``conductor.ai.agents.exceptions`` (which now re-exports these same objects, -so ``except AgentspanError`` catches errors raised by either layer). +This module is the canonical home of the hierarchy also re-exported from +``conductor.ai.agents.exceptions``. """ from __future__ import annotations -class AgentspanError(Exception): +class ConductorAgentError(Exception): """Base exception for all agent SDK errors.""" -class AgentAPIError(AgentspanError): +class AgentAPIError(ConductorAgentError): """An HTTP error from the agent runtime API.""" def __init__(self, status_code: int, message: str, url: str = ""): diff --git a/src/conductor/client/ai/schedule.py b/src/conductor/client/ai/schedule.py index 24b73a980..41b28bdd7 100644 --- a/src/conductor/client/ai/schedule.py +++ b/src/conductor/client/ai/schedule.py @@ -1,6 +1,3 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """User-facing dataclasses: ``Schedule`` (input) and ``ScheduleInfo`` (output), plus the mapping layer between them and Conductor's ``SchedulerClient`` models. diff --git a/src/conductor/client/ai/schedule_errors.py b/src/conductor/client/ai/schedule_errors.py index a11a292d5..e603b5a2e 100644 --- a/src/conductor/client/ai/schedule_errors.py +++ b/src/conductor/client/ai/schedule_errors.py @@ -1,14 +1,11 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Schedule-specific exceptions.""" from __future__ import annotations -from conductor.client.ai.agent_errors import AgentspanError +from conductor.client.ai.agent_errors import ConductorAgentError -class ScheduleError(AgentspanError): +class ScheduleError(ConductorAgentError): """Base class for schedule errors.""" diff --git a/src/conductor/client/automator/worker_isolation.py b/src/conductor/client/automator/worker_isolation.py index e37e69daa..00a3e6062 100644 --- a/src/conductor/client/automator/worker_isolation.py +++ b/src/conductor/client/automator/worker_isolation.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Worker isolation mode — ``process`` (default) or ``thread``. ``CONDUCTOR_WORKER_ISOLATION=thread`` replaces :class:`TaskHandler`'s diff --git a/src/conductor/client/configuration/configuration.py b/src/conductor/client/configuration/configuration.py index f42880f27..71bcec86e 100644 --- a/src/conductor/client/configuration/configuration.py +++ b/src/conductor/client/configuration/configuration.py @@ -39,7 +39,7 @@ def __init__( elif base_url is not None: self.host = base_url + "/api" else: - self.host = os.getenv("CONDUCTOR_SERVER_URL") or os.getenv("AGENTSPAN_SERVER_URL") + self.host = os.getenv("CONDUCTOR_SERVER_URL") or os.getenv("CONDUCTOR_SERVER_URL") if self.host is None or self.host == "": self.host = "http://localhost:8080/api" @@ -64,12 +64,12 @@ def __init__( self.debug = debug # Explicit SDK-wide log level: a level name ("INFO", "DEBUG", …) or an # int. Overrides the debug-derived default; falls back to the - # CONDUCTOR_LOG_LEVEL / AGENTSPAN_LOG_LEVEL env vars. Applies to every + # CONDUCTOR_LOG_LEVEL / CONDUCTOR_LOG_LEVEL env vars. Applies to every # SDK component (clients, workers, agent runtime). resolved_level = ( log_level if log_level is not None - else (os.getenv("CONDUCTOR_LOG_LEVEL") or os.getenv("AGENTSPAN_LOG_LEVEL")) + else (os.getenv("CONDUCTOR_LOG_LEVEL") or os.getenv("CONDUCTOR_LOG_LEVEL")) ) if resolved_level is not None and str(resolved_level).strip() != "": self.__log_level = ( diff --git a/src/conductor/client/orkes/orkes_agent_client.py b/src/conductor/client/orkes/orkes_agent_client.py index 7c360bd8b..8de36f0c1 100644 --- a/src/conductor/client/orkes/orkes_agent_client.py +++ b/src/conductor/client/orkes/orkes_agent_client.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Orkes implementation of :class:`AgentClient` for the ``/agent/*`` control plane. All wire calls go through the shared :class:`ApiClient` (sync) / diff --git a/tests/integration/ai/__init__.py b/tests/integration/ai/__init__.py index 993845f28..e69de29bb 100644 --- a/tests/integration/ai/__init__.py +++ b/tests/integration/ai/__init__.py @@ -1,3 +0,0 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - diff --git a/tests/integration/ai/conftest.py b/tests/integration/ai/conftest.py index c3193ec82..54d2d4c4b 100644 --- a/tests/integration/ai/conftest.py +++ b/tests/integration/ai/conftest.py @@ -1,12 +1,9 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Shared fixtures for integration tests. Provides a module-scoped AgentRuntime and a configurable LLM model. SSE streaming is enabled by default. Disable explicitly with -``AGENTSPAN_STREAMING_ENABLED=false`` if the server does not support SSE. +``CONDUCTOR_AGENT_STREAMING_ENABLED=false`` if the server does not support SSE. """ import os @@ -20,8 +17,8 @@ from conductor.ai.agents import AgentRuntime from conductor.ai.agents.runtime.config import AgentConfig -DEFAULT_MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") -_SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +DEFAULT_MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") +_SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") def _conductor_base() -> str: @@ -146,7 +143,7 @@ def _check(self) -> None: try: os.kill(pid, signal.SIGKILL) import logging - logging.getLogger("agentspan.test.watchdog").warning( + logging.getLogger("conductor_agent.test.watchdog").warning( "Killed stuck worker pid=%s for task_type=%s " "(queue_depth=%s, stuck=%.0fs)", pid, task_type, depth, now - first_seen, @@ -179,5 +176,5 @@ def runtime(): @pytest.fixture def model(): - """LLM model string, overridable via AGENTSPAN_LLM_MODEL env var.""" + """LLM model string, overridable via CONDUCTOR_AGENT_LLM_MODEL env var.""" return DEFAULT_MODEL diff --git a/tests/integration/ai/test_behavioral_correctness_live.py b/tests/integration/ai/test_behavioral_correctness_live.py index d89e60fc6..d9776f0a0 100644 --- a/tests/integration/ai/test_behavioral_correctness_live.py +++ b/tests/integration/ai/test_behavioral_correctness_live.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Behavioral correctness tests — deeper multi-agent verification with real LLMs. Unlike test_correctness_live.py which checks "did the right agent run?", these @@ -14,7 +11,7 @@ - SWARM: Multiple agents participate in a single request when context demands it Requires: - - Agentspan server running (AGENTSPAN_SERVER_URL) + - Conductor Agents server running (CONDUCTOR_SERVER_URL) - OPENAI_API_KEY set Run with: diff --git a/tests/integration/ai/test_correctness_live.py b/tests/integration/ai/test_correctness_live.py index e02250ac5..a5620527a 100644 --- a/tests/integration/ai/test_correctness_live.py +++ b/tests/integration/ai/test_correctness_live.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Live correctness tests — runs real agents with real LLM calls. These tests verify that orchestration strategies actually work end-to-end: @@ -12,7 +9,7 @@ - Strategy validators catch real violations Requires: - - Agentspan server running (AGENTSPAN_SERVER_URL) + - Conductor Agents server running (CONDUCTOR_SERVER_URL) - OPENAI_API_KEY set Run with: diff --git a/tests/integration/ai/test_e2e_sse.py b/tests/integration/ai/test_e2e_sse.py index 0b237d9a7..1cf0745c0 100644 --- a/tests/integration/ai/test_e2e_sse.py +++ b/tests/integration/ai/test_e2e_sse.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tier 3: Real server SSE integration tests. Tests the full Python SDK → Java Runtime → Conductor → SSE path. @@ -37,7 +34,7 @@ def _model() -> str: - return os.environ.get("AGENTSPAN_LLM_MODEL", DEFAULT_MODEL) + return os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", DEFAULT_MODEL) def _unique_name(prefix: str) -> str: diff --git a/tests/integration/ai/test_e2e_streaming.py b/tests/integration/ai/test_e2e_streaming.py index 4f35a7101..6ac2bff3e 100644 --- a/tests/integration/ai/test_e2e_streaming.py +++ b/tests/integration/ai/test_e2e_streaming.py @@ -1,15 +1,12 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """E2E streaming tests — validate complete SSE event streams for all agent categories. These tests require a running Conductor server with LLM and streaming support. Skip with: pytest -m "not integration" Requirements: - - export AGENTSPAN_SERVER_URL=http://localhost:8080/api + - export CONDUCTOR_SERVER_URL=http://localhost:8080/api - LLM provider configured (OpenAI by default) - - Optionally: export AGENTSPAN_LLM_MODEL=openai/gpt-4o-mini + - Optionally: export CONDUCTOR_AGENT_LLM_MODEL=openai/gpt-4o-mini """ import re @@ -41,7 +38,7 @@ def _model() -> str: import os - return os.environ.get("AGENTSPAN_LLM_MODEL", DEFAULT_MODEL) + return os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", DEFAULT_MODEL) # ── Data-Driven Validation ────────────────────────────────────────────── diff --git a/tests/integration/ai/test_guardrail_matrix.py b/tests/integration/ai/test_guardrail_matrix.py index e8849dc7a..f70c62eca 100644 --- a/tests/integration/ai/test_guardrail_matrix.py +++ b/tests/integration/ai/test_guardrail_matrix.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Guardrail matrix integration tests — full 3x3x3 coverage, parallel execution. All 27 workflows are fired concurrently via runtime.start(), then polled to @@ -13,7 +10,7 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - AGENT_LLM_MODEL set (default: anthropic/claude-sonnet-4-6) """ diff --git a/tests/integration/ai/test_lease_extension.py b/tests/integration/ai/test_lease_extension.py index 6c0134937..3318a37df 100644 --- a/tests/integration/ai/test_lease_extension.py +++ b/tests/integration/ai/test_lease_extension.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """E2E test for Conductor lease extension (heartbeat) behavior. The Python SDK registers all worker tasks with ``response_timeout_seconds=10`` @@ -13,7 +10,7 @@ Requirements: - Running Conductor server with conductor-python >= 1.3.11 - - export AGENTSPAN_SERVER_URL=http://localhost:8080/api + - export CONDUCTOR_SERVER_URL=http://localhost:8080/api - LLM provider configured """ @@ -38,7 +35,7 @@ def _model() -> str: - return os.environ.get("AGENTSPAN_LLM_MODEL", DEFAULT_MODEL) + return os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", DEFAULT_MODEL) def _unique_name(prefix: str) -> str: diff --git a/tests/integration/ai/test_multi_agent_matrix.py b/tests/integration/ai/test_multi_agent_matrix.py index ef37f693b..40e404131 100644 --- a/tests/integration/ai/test_multi_agent_matrix.py +++ b/tests/integration/ai/test_multi_agent_matrix.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Multi-agent orchestration matrix integration tests — 21 tests, parallel execution. All 21 workflows fire concurrently via runtime.start(), then polled to @@ -14,7 +11,7 @@ Requirements: - Conductor server running - - AGENTSPAN_SERVER_URL=http://localhost:8080/api + - CONDUCTOR_SERVER_URL=http://localhost:8080/api - AGENT_LLM_MODEL set (default: anthropic/claude-sonnet-4-6) """ diff --git a/tests/integration/ai/test_pac_toolType_routing_e2e.py b/tests/integration/ai/test_pac_toolType_routing_e2e.py index 3072b7f06..722c1a25e 100644 --- a/tests/integration/ai/test_pac_toolType_routing_e2e.py +++ b/tests/integration/ai/test_pac_toolType_routing_e2e.py @@ -1,9 +1,6 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """End-to-end test for PAC tool-type routing. -Drives ``Strategy.PLAN_EXECUTE`` against a live agentspan server with a typed +Drives ``Strategy.PLAN_EXECUTE`` against a live Conductor Agents server with a typed Plan that mixes four tool types in one workflow: - 2 × ``mcp`` tools (mcp-testkit math_add + string_uppercase) @@ -32,7 +29,7 @@ asserts every routed task transitioned to status=COMPLETED. Requirements (the test SKIPs cleanly if either is absent): - - agentspan server reachable at ``AGENTSPAN_SERVER_URL`` (default + - Conductor Agents server reachable at ``CONDUCTOR_SERVER_URL`` (default http://localhost:8080/api) with the PAC tool-type routing fix - mcp-testkit running on http://localhost:3001/mcp (``uv run mcp-testkit --transport http --port 3001``) @@ -55,14 +52,14 @@ pytestmark = pytest.mark.integration -AGENTSPAN_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") -CONDUCTOR_BASE = AGENTSPAN_URL.replace("/api", "") +CONDUCTOR_AGENT_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") +CONDUCTOR_BASE = CONDUCTOR_AGENT_URL.replace("/api", "") MCP_URL = "http://localhost:3001/mcp" -def _agentspan_up() -> bool: +def _conductor_agent_up() -> bool: try: - return requests.get(f"{AGENTSPAN_URL}/metadata/workflow", timeout=2).status_code == 200 + return requests.get(f"{CONDUCTOR_AGENT_URL}/metadata/workflow", timeout=2).status_code == 200 except Exception: # noqa: BLE001 return False @@ -111,7 +108,7 @@ def _mcp_static_tool(name: str, description: str, input_schema: dict) -> ToolDef mini_agent = Agent( name="mini_agent_e2e", - model=os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini"), + model=os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini"), instructions=( "Reply with EXACTLY the single token 'AGENT_OK' and nothing else. " "No punctuation, no whitespace, no preamble, no explanation." @@ -194,7 +191,7 @@ def _collect_task_types(tasks: list[dict]) -> list[tuple[str, str]]: # ── The test ────────────────────────────────────────────────────────── -@pytest.mark.skipif(not _agentspan_up(), reason="agentspan server not running") +@pytest.mark.skipif(not _conductor_agent_up(), reason="Conductor Agents server not running") @pytest.mark.skipif(not _mcp_up(), reason="mcp-testkit not running on :3001") def test_pac_toolType_routing_end_to_end() -> None: """Single end-to-end run that proves PAC compiles each toolType to @@ -205,7 +202,7 @@ def test_pac_toolType_routing_end_to_end() -> None: name="pac_routing_e2e", tools=[math_add, string_uppercase, agent_tool(mini_agent), stitch_e2e], planner_instructions="", # typed Plan injected; planner output discarded - model=os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini"), + model=os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini"), ) plan = Plan( diff --git a/tests/integration/ai/test_plan_execute_live.py b/tests/integration/ai/test_plan_execute_live.py index 632daa8d6..a161b703a 100644 --- a/tests/integration/ai/test_plan_execute_live.py +++ b/tests/integration/ai/test_plan_execute_live.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Plan-Execute strategy e2e tests — runs real agents with real LLM calls. Tests the PLAN_EXECUTE strategy end-to-end: @@ -12,7 +9,7 @@ - Files are actually created on disk Requires: - - Agentspan server running (AGENTSPAN_SERVER_URL) + - Conductor Agents server running (CONDUCTOR_SERVER_URL) - OPENAI_API_KEY set Run with: @@ -35,7 +32,7 @@ tool, ) -_SERVER_URL = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") +_SERVER_URL = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") pytestmark = pytest.mark.integration diff --git a/tests/integration/ai/test_retry_policy.py b/tests/integration/ai/test_retry_policy.py index 3b805fdc8..0a7d901ba 100644 --- a/tests/integration/ai/test_retry_policy.py +++ b/tests/integration/ai/test_retry_policy.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """E2E test: verify retry_policy on @tool flows through to Conductor TaskDef. Registers tools with different retry policies, then queries the Conductor @@ -8,8 +5,8 @@ and retryDelaySeconds. Requirements: - - Agentspan server running - - export AGENTSPAN_SERVER_URL=http://localhost:8080/api (or via env) + - Conductor Agents server running + - export CONDUCTOR_SERVER_URL=http://localhost:8080/api (or via env) """ import os @@ -22,7 +19,7 @@ pytestmark = pytest.mark.integration -_MODEL = os.environ.get("AGENTSPAN_LLM_MODEL", "openai/gpt-4o-mini") +_MODEL = os.environ.get("CONDUCTOR_AGENT_LLM_MODEL", "openai/gpt-4o-mini") @tool(retry_policy="fixed", retry_count=4, retry_delay_seconds=7) @@ -65,7 +62,7 @@ def _retry_default_tool(x: str) -> str: def _conductor_base() -> str: - url = os.environ.get("AGENTSPAN_SERVER_URL", "http://localhost:8080/api") + url = os.environ.get("CONDUCTOR_SERVER_URL", "http://localhost:8080/api") return url.rstrip("/").replace("/api", "") diff --git a/tests/integration/ai/test_token_usage.py b/tests/integration/ai/test_token_usage.py index 187046a7c..65bcad94f 100644 --- a/tests/integration/ai/test_token_usage.py +++ b/tests/integration/ai/test_token_usage.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Token usage collection tests. Validates that AgentResult.token_usage is populated correctly for: @@ -9,8 +6,8 @@ - Parallel agents (all sub-workflows aggregated) Requires: - - Agentspan server running (AGENTSPAN_SERVER_URL) - - LLM integration configured (AGENTSPAN_LLM_MODEL) + - Conductor Agents server running (CONDUCTOR_SERVER_URL) + - LLM integration configured (CONDUCTOR_AGENT_LLM_MODEL) Run with: python3 -m pytest tests/integration/test_token_usage.py -v -s diff --git a/tests/unit/ai/__init__.py b/tests/unit/ai/__init__.py index 993845f28..e69de29bb 100644 --- a/tests/unit/ai/__init__.py +++ b/tests/unit/ai/__init__.py @@ -1,3 +0,0 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - diff --git a/tests/unit/ai/conftest.py b/tests/unit/ai/conftest.py index fad5a6358..355e26cd0 100644 --- a/tests/unit/ai/conftest.py +++ b/tests/unit/ai/conftest.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit-test fixtures — clears global dispatch state between tests.""" import pytest diff --git a/tests/unit/ai/credentials/test_accessor.py b/tests/unit/ai/credentials/test_accessor.py index 628611481..34d4bb9c7 100644 --- a/tests/unit/ai/credentials/test_accessor.py +++ b/tests/unit/ai/credentials/test_accessor.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for get_secret() accessor.""" import pytest diff --git a/tests/unit/ai/credentials/test_public_api.py b/tests/unit/ai/credentials/test_public_api.py index 8e00aa1a4..f2fc9c35d 100644 --- a/tests/unit/ai/credentials/test_public_api.py +++ b/tests/unit/ai/credentials/test_public_api.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Verify that credential types are exported from the top-level conductor.ai.agents package.""" diff --git a/tests/unit/ai/credentials/test_types.py b/tests/unit/ai/credentials/test_types.py index 13d685d2c..000aca603 100644 --- a/tests/unit/ai/credentials/test_types.py +++ b/tests/unit/ai/credentials/test_types.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for credential exception hierarchy.""" from conductor.ai.agents.runtime.credentials.types import ( @@ -9,15 +6,15 @@ CredentialRateLimitError, CredentialServiceError, ) -from conductor.ai.agents.exceptions import AgentspanError +from conductor.ai.agents.exceptions import ConductorAgentError class TestCredentialExceptions: """Exception hierarchy.""" - def test_credential_not_found_error_is_agentspan_error(self): + def test_credential_not_found_error_is_conductor_agent_error(self): exc = CredentialNotFoundError(["GITHUB_TOKEN"]) - assert isinstance(exc, AgentspanError) + assert isinstance(exc, ConductorAgentError) def test_credential_not_found_error_message_contains_names(self): exc = CredentialNotFoundError(["GITHUB_TOKEN", "OPENAI_API_KEY"]) @@ -28,21 +25,21 @@ def test_credential_not_found_error_stores_names(self): exc = CredentialNotFoundError(["GITHUB_TOKEN"]) assert exc.missing_names == ["GITHUB_TOKEN"] - def test_credential_auth_error_is_agentspan_error(self): + def test_credential_auth_error_is_conductor_agent_error(self): exc = CredentialAuthError("token expired") - assert isinstance(exc, AgentspanError) + assert isinstance(exc, ConductorAgentError) def test_credential_auth_error_message(self): exc = CredentialAuthError("token expired") assert "token expired" in str(exc) - def test_credential_rate_limit_error_is_agentspan_error(self): + def test_credential_rate_limit_error_is_conductor_agent_error(self): exc = CredentialRateLimitError() - assert isinstance(exc, AgentspanError) + assert isinstance(exc, ConductorAgentError) - def test_credential_service_error_is_agentspan_error(self): + def test_credential_service_error_is_conductor_agent_error(self): exc = CredentialServiceError(503, "unavailable") - assert isinstance(exc, AgentspanError) + assert isinstance(exc, ConductorAgentError) def test_credential_service_error_stores_status_code(self): exc = CredentialServiceError(503, "unavailable") diff --git a/tests/unit/ai/test_agent.py b/tests/unit/ai/test_agent.py index b3eacdd7e..9db719758 100644 --- a/tests/unit/ai/test_agent.py +++ b/tests/unit/ai/test_agent.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the Agent class.""" import pytest diff --git a/tests/unit/ai/test_agent_decorator.py b/tests/unit/ai/test_agent_decorator.py index 4dee27ec1..074709b2f 100644 --- a/tests/unit/ai/test_agent_decorator.py +++ b/tests/unit/ai/test_agent_decorator.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the @agent decorator, AgentDef, and external agents.""" import pytest diff --git a/tests/unit/ai/test_agent_error_alias.py b/tests/unit/ai/test_agent_error_alias.py new file mode 100644 index 000000000..c9dd053a7 --- /dev/null +++ b/tests/unit/ai/test_agent_error_alias.py @@ -0,0 +1,8 @@ +"""Public export tests for the Conductor Agents error hierarchy.""" + +from conductor.ai.agents import ConductorAgentError +from conductor.client.ai import ConductorAgentError as ClientError + + +def test_conductor_agent_error_is_the_canonical_error_class(): + assert ClientError is ConductorAgentError diff --git a/tests/unit/ai/test_agent_handle_join.py b/tests/unit/ai/test_agent_handle_join.py index a1752a549..b3ee569a8 100644 --- a/tests/unit/ai/test_agent_handle_join.py +++ b/tests/unit/ai/test_agent_handle_join.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for AgentHandle.join() and AgentHandle.join_async().""" import asyncio diff --git a/tests/unit/ai/test_agent_http.py b/tests/unit/ai/test_agent_http.py index 83883c075..3c8c8fe45 100644 --- a/tests/unit/ai/test_agent_http.py +++ b/tests/unit/ai/test_agent_http.py @@ -1,7 +1,4 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - -"""Unit tests for ``agent_http`` — the worker HTTP path that posts to Agentspan +"""Unit tests for ``agent_http`` — the worker HTTP path that posts to Conductor Agents ``/agent/*`` endpoints through the SDK ``ApiClient``. Uses a real in-process HTTP server (no mocks, per repo test policy) serving both diff --git a/tests/unit/ai/test_agent_schema_contract.py b/tests/unit/ai/test_agent_schema_contract.py new file mode 100644 index 000000000..eecd1db33 --- /dev/null +++ b/tests/unit/ai/test_agent_schema_contract.py @@ -0,0 +1,51 @@ +"""Keep the documented agent wire contract aligned with the serializer.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import jsonschema +import pytest + +from conductor.ai.agents.agent import Agent +from conductor.ai.agents.config_serializer import AgentConfigSerializer +from conductor.ai.agents.guardrail import RegexGuardrail +from conductor.ai.agents.termination import TextMentionTermination +from conductor.ai.agents.tool import tool + + +ROOT = Path(__file__).resolve().parents[3] +SCHEMA_PATH = ROOT / "docs" / "agents" / "reference" / "agent-schema.json" + + +@tool +def _schema_tool(query: str) -> dict: + """Return a deterministic result for schema validation.""" + return {"query": query} + + +def test_agent_schema_is_valid_and_accepts_representative_serializer_output(): + schema = json.loads(SCHEMA_PATH.read_text()) + jsonschema.Draft202012Validator.check_schema(schema) + agent = Agent( + name="schema_root", + model="openai/gpt-4o-mini", + instructions="Return a concise answer.", + tools=[_schema_tool], + agents=[Agent(name="schema_child", model="openai/gpt-4o-mini")], + strategy="handoff", + guardrails=[RegexGuardrail(name="safe", patterns=[".*"])], + termination=TextMentionTermination(text="DONE"), + metadata={"owner": "docs"}, + max_tokens=100, + temperature=0.1, + ) + payload = AgentConfigSerializer().serialize(agent) + jsonschema.validate(payload, schema) + + +def test_agent_schema_rejects_unknown_root_fields(): + schema = json.loads(SCHEMA_PATH.read_text()) + with pytest.raises(jsonschema.ValidationError): + jsonschema.validate({"name": "valid_name", "unknownField": True}, schema) diff --git a/tests/unit/ai/test_async_stream.py b/tests/unit/ai/test_async_stream.py index 86c5288e4..543a0bee7 100644 --- a/tests/unit/ai/test_async_stream.py +++ b/tests/unit/ai/test_async_stream.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for AsyncAgentStream.""" from __future__ import annotations diff --git a/tests/unit/ai/test_claude_agent_sdk_worker.py b/tests/unit/ai/test_claude_agent_sdk_worker.py index 4f2c2b222..dc3aea90a 100644 --- a/tests/unit/ai/test_claude_agent_sdk_worker.py +++ b/tests/unit/ai/test_claude_agent_sdk_worker.py @@ -174,7 +174,7 @@ def test_worker_uses_cwd_from_task_input(self): assert result.status == "COMPLETED" -class TestAgentspanHooks: +class TestConductorAgentHooks: def _make_metadata(self): return { "tool_call_count": 0, @@ -191,10 +191,10 @@ def _make_metadata(self): } def test_build_hooks_returns_dict_with_expected_keys(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) assert "PreToolUse" in hooks assert "PostToolUse" in hooks @@ -205,7 +205,7 @@ def test_build_hooks_returns_dict_with_expected_keys(self): assert "Stop" in hooks def test_pre_tool_use_hook_increments_metadata(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() @@ -213,7 +213,7 @@ def test_pre_tool_use_hook_increments_metadata(self): patch("conductor.ai.agents.frameworks.claude_agent_sdk._push_event_nonblocking"), patch("conductor.ai.agents.frameworks.claude_agent_sdk._inject_tool_task", return_value=True), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) pre_hook = hooks["PreToolUse"][0].hooks[0] result = asyncio.run( pre_hook( @@ -229,7 +229,7 @@ def test_pre_tool_use_hook_increments_metadata(self): assert result == {} def test_hooks_push_events(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks pushed = [] metadata = self._make_metadata() @@ -244,7 +244,7 @@ def capture_push(exec_id, event, *args): ), patch("conductor.ai.agents.frameworks.claude_agent_sdk._inject_tool_task", return_value=True), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) pre_hook = hooks["PreToolUse"][0].hooks[0] asyncio.run( pre_hook( @@ -260,7 +260,7 @@ def capture_push(exec_id, event, *args): assert pushed[0]["toolUseId"] == "tu-3" def test_post_tool_use_hook_pushes_event(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks pushed = [] metadata = self._make_metadata() @@ -276,7 +276,7 @@ def capture_push(exec_id, event, *args): patch("conductor.ai.agents.frameworks.claude_agent_sdk._update_task_progress_nonblocking"), patch("conductor.ai.agents.frameworks.claude_agent_sdk._complete_tool_task_nonblocking"), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) post_hook = hooks["PostToolUse"][0].hooks[0] asyncio.run( post_hook( @@ -292,7 +292,7 @@ def capture_push(exec_id, event, *args): assert pushed[0]["toolUseId"] == "tu-5" def test_post_tool_use_hook_tracks_last_output(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() @@ -301,7 +301,7 @@ def test_post_tool_use_hook_tracks_last_output(self): patch("conductor.ai.agents.frameworks.claude_agent_sdk._update_task_progress_nonblocking"), patch("conductor.ai.agents.frameworks.claude_agent_sdk._complete_tool_task_nonblocking"), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) pre_hook = hooks["PreToolUse"][0].hooks[0] post_hook = hooks["PostToolUse"][0].hooks[0] # Pre creates the entry @@ -329,7 +329,7 @@ def test_post_tool_use_hook_tracks_last_output(self): def test_post_tool_use_hook_throttles_progress_updates(self): import time as time_mod - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() # Pretend the last progress update was just now @@ -340,7 +340,7 @@ def test_post_tool_use_hook_throttles_progress_updates(self): patch("conductor.ai.agents.frameworks.claude_agent_sdk._update_task_progress_nonblocking") as mock_progress, patch("conductor.ai.agents.frameworks.claude_agent_sdk._complete_tool_task_nonblocking"), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) post_hook = hooks["PostToolUse"][0].hooks[0] # Two rapid calls — should NOT trigger progress update (throttled) asyncio.run(post_hook({"tool_name": "Read", "hook_event_name": "PostToolUse"}, "tu-7", None)) @@ -349,7 +349,7 @@ def test_post_tool_use_hook_throttles_progress_updates(self): assert mock_progress.call_count == 0 def test_post_tool_use_hook_sends_progress_after_interval(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() # Pretend the last progress update was long ago @@ -360,7 +360,7 @@ def test_post_tool_use_hook_sends_progress_after_interval(self): patch("conductor.ai.agents.frameworks.claude_agent_sdk._update_task_progress_nonblocking") as mock_progress, patch("conductor.ai.agents.frameworks.claude_agent_sdk._complete_tool_task_nonblocking"), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) post_hook = hooks["PostToolUse"][0].hooks[0] asyncio.run(post_hook({"tool_name": "Bash", "hook_event_name": "PostToolUse"}, "tu-9", None)) @@ -369,7 +369,7 @@ def test_post_tool_use_hook_sends_progress_after_interval(self): assert mock_progress.call_args[0][1] == "wf-1" # execution_id def test_post_tool_use_failure_hook_tracks_errors(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() @@ -378,7 +378,7 @@ def test_post_tool_use_failure_hook_tracks_errors(self): patch("conductor.ai.agents.frameworks.claude_agent_sdk._update_task_progress_nonblocking"), patch("conductor.ai.agents.frameworks.claude_agent_sdk._complete_tool_task_nonblocking"), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) pre_hook = hooks["PreToolUse"][0].hooks[0] failure_hook = hooks["PostToolUseFailure"][0].hooks[0] # Pre creates the entry @@ -404,7 +404,7 @@ def test_post_tool_use_failure_hook_tracks_errors(self): def test_agent_tool_deferred_to_subagent_start(self): """PreToolUse(Agent) does NOT inject a SIMPLE task — it defers to SubagentStart.""" - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() inject_calls = [] @@ -418,7 +418,7 @@ def capture_inject(*args, **kwargs): patch("conductor.ai.agents.frameworks.claude_agent_sdk._create_tracking_workflow", return_value="sub-exec-42"), patch("conductor.ai.agents.frameworks.claude_agent_sdk._inject_tool_task", side_effect=capture_inject), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) pre_hook = hooks["PreToolUse"][0].hooks[0] start_hook = hooks["SubagentStart"][0].hooks[0] @@ -440,7 +440,7 @@ def capture_inject(*args, **kwargs): def test_full_subagent_lifecycle(self): """Full lifecycle: PreToolUse(Agent) → SubagentStart → SubagentStop → PostToolUse(Agent).""" - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() @@ -452,7 +452,7 @@ def test_full_subagent_lifecycle(self): patch("conductor.ai.agents.frameworks.claude_agent_sdk._complete_workflow_nonblocking") as mock_complete_wf, patch("conductor.ai.agents.frameworks.claude_agent_sdk._update_task_progress_nonblocking"), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) pre_hook = hooks["PreToolUse"][0].hooks[0] start_hook = hooks["SubagentStart"][0].hooks[0] stop_hook = hooks["SubagentStop"][0].hooks[0] @@ -478,7 +478,7 @@ def test_full_subagent_lifecycle(self): assert mock_complete_wf.call_args[0][0] == "sub-exec-42" def test_stop_hook_pushes_agent_stop_event(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks pushed = [] metadata = self._make_metadata() @@ -490,7 +490,7 @@ def capture_push(exec_id, event, *args): "conductor.ai.agents.frameworks.claude_agent_sdk._push_event_nonblocking", side_effect=capture_push, ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) stop_hook = hooks["Stop"][0].hooks[0] asyncio.run(stop_hook({}, None, None)) @@ -498,7 +498,7 @@ def capture_push(exec_id, event, *args): assert pushed[0]["type"] == "agent_stop" def test_hooks_are_defensive(self): - from conductor.ai.agents.frameworks.claude_agent_sdk import _build_agentspan_hooks + from conductor.ai.agents.frameworks.claude_agent_sdk import _build_conductor_agent_hooks metadata = self._make_metadata() @@ -506,7 +506,7 @@ def test_hooks_are_defensive(self): "conductor.ai.agents.frameworks.claude_agent_sdk._push_event_nonblocking", side_effect=RuntimeError("network down"), ): - hooks = _build_agentspan_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) + hooks = _build_conductor_agent_hooks("t-1", "wf-1", "http://localhost", "k", "s", metadata) pre_hook = hooks["PreToolUse"][0].hooks[0] result = asyncio.run( pre_hook( @@ -528,8 +528,8 @@ def test_merge_with_no_user_hooks(self): options = _make_options() options.hooks = None - agentspan_hooks = {"PreToolUse": [SdkHookMatcher(hooks=[lambda d, t, c: {}])]} - merged = _merge_hooks(options, agentspan_hooks) + conductor_agent_hooks = {"PreToolUse": [SdkHookMatcher(hooks=[lambda d, t, c: {}])]} + merged = _merge_hooks(options, conductor_agent_hooks) result_hooks = merged.hooks if hasattr(merged, "hooks") else merged.get("hooks", {}) assert len(result_hooks["PreToolUse"]) == 1 @@ -542,15 +542,15 @@ def test_merge_preserves_user_hooks_first(self): user_matcher = SdkHookMatcher(matcher="Bash", hooks=[lambda d, t, c: {}]) options.hooks = {"PreToolUse": [user_matcher]} - agentspan_matcher = SdkHookMatcher(hooks=[lambda d, t, c: {}]) - agentspan_hooks = {"PreToolUse": [agentspan_matcher]} + conductor_agent_matcher = SdkHookMatcher(hooks=[lambda d, t, c: {}]) + conductor_agent_hooks = {"PreToolUse": [conductor_agent_matcher]} - merged = _merge_hooks(options, agentspan_hooks) + merged = _merge_hooks(options, conductor_agent_hooks) result_hooks = merged.hooks if hasattr(merged, "hooks") else merged.get("hooks", {}) assert len(result_hooks["PreToolUse"]) == 2 assert result_hooks["PreToolUse"][0] is user_matcher - assert result_hooks["PreToolUse"][1] is agentspan_matcher + assert result_hooks["PreToolUse"][1] is conductor_agent_matcher def test_merge_combines_different_events(self): from conductor.ai.agents.frameworks.claude_agent_sdk import _merge_hooks @@ -560,9 +560,9 @@ def test_merge_combines_different_events(self): user_matcher = SdkHookMatcher(hooks=[lambda d, t, c: {}]) options.hooks = {"Stop": [user_matcher]} - agentspan_hooks = {"PreToolUse": [SdkHookMatcher(hooks=[lambda d, t, c: {}])]} + conductor_agent_hooks = {"PreToolUse": [SdkHookMatcher(hooks=[lambda d, t, c: {}])]} - merged = _merge_hooks(options, agentspan_hooks) + merged = _merge_hooks(options, conductor_agent_hooks) result_hooks = merged.hooks if hasattr(merged, "hooks") else merged.get("hooks", {}) assert "Stop" in result_hooks diff --git a/tests/unit/ai/test_cli_config.py b/tests/unit/ai/test_cli_config.py index 60e344a08..0c6a96f55 100644 --- a/tests/unit/ai/test_cli_config.py +++ b/tests/unit/ai/test_cli_config.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for CLI command execution configuration and tool.""" import subprocess from unittest.mock import MagicMock, patch @@ -138,11 +135,11 @@ def test_full_command_line_in_command_is_tokenized(self): with patch("conductor.ai.agents.cli_config.subprocess.run") as mock_run: mock_run.return_value = MagicMock(returncode=0, stdout="[]\n", stderr="") result = tool_fn.__wrapped__( - command="gh repo list agentspan --limit 5 --json name,updatedAt" + command="gh repo list conductor_agent --limit 5 --json name,updatedAt" ) assert result["status"] == "success" mock_run.assert_called_once_with( - ["gh", "repo", "list", "agentspan", "--limit", "5", "--json", "name,updatedAt"], + ["gh", "repo", "list", "conductor_agent", "--limit", "5", "--json", "name,updatedAt"], capture_output=True, text=True, timeout=30, diff --git a/tests/unit/ai/test_code_execution.py b/tests/unit/ai/test_code_execution.py index 8003cdace..ecb2ccfb0 100644 --- a/tests/unit/ai/test_code_execution.py +++ b/tests/unit/ai/test_code_execution.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for first-class code execution support.""" import pytest diff --git a/tests/unit/ai/test_code_executor.py b/tests/unit/ai/test_code_executor.py index 469ace9ea..f0c183f38 100644 --- a/tests/unit/ai/test_code_executor.py +++ b/tests/unit/ai/test_code_executor.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for code executors — LocalCodeExecutor, DockerCodeExecutor, JupyterCodeExecutor, ServerlessCodeExecutor. """ diff --git a/tests/unit/ai/test_compiler.py b/tests/unit/ai/test_compiler.py index af43f84f0..a01f67c90 100644 --- a/tests/unit/ai/test_compiler.py +++ b/tests/unit/ai/test_compiler.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for internal utilities (model parser, schema generation).""" import pytest diff --git a/tests/unit/ai/test_config_env.py b/tests/unit/ai/test_config_env.py index 864b3affd..7a0885d3f 100644 --- a/tests/unit/ai/test_config_env.py +++ b/tests/unit/ai/test_config_env.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for AgentConfig environment loading and SDK-wide log-level config. AgentConfig holds only agent-runtime settings (worker pool + liveness). @@ -27,16 +24,16 @@ class TestEnvHelper: """Tests for the _env() helper function.""" def test_reads_var(self): - with mock.patch.dict(os.environ, {"AGENTSPAN_FOO": "bar"}, clear=False): - assert _env("AGENTSPAN_FOO") == "bar" + with mock.patch.dict(os.environ, {"CONDUCTOR_AGENT_FOO": "bar"}, clear=False): + assert _env("CONDUCTOR_AGENT_FOO") == "bar" def test_returns_default_when_not_set(self): with mock.patch.dict(os.environ, {}, clear=True): - assert _env("AGENTSPAN_FOO", "default") == "default" + assert _env("CONDUCTOR_AGENT_FOO", "default") == "default" def test_returns_none_when_no_default(self): with mock.patch.dict(os.environ, {}, clear=True): - assert _env("AGENTSPAN_FOO") is None + assert _env("CONDUCTOR_AGENT_FOO") is None class TestEnvBool: @@ -80,6 +77,10 @@ def test_empty_string_uses_default(self): with mock.patch.dict(os.environ, {"NUM": ""}, clear=True): assert _env_int("NUM", 7) == 7 + def test_invalid_value_uses_default(self): + with mock.patch.dict(os.environ, {"NUM": "not-a-number"}, clear=True): + assert _env_int("NUM", 7) == 7 + class TestEnvFloat: """Tests for _env_float() helper.""" @@ -96,6 +97,10 @@ def test_empty_string_uses_default(self): with mock.patch.dict(os.environ, {"SECS": ""}, clear=True): assert _env_float("SECS", 30.0) == 30.0 + def test_invalid_value_uses_default(self): + with mock.patch.dict(os.environ, {"SECS": "not-a-number"}, clear=True): + assert _env_float("SECS", 30.0) == 30.0 + class TestAgentConfigFromEnv: """Tests for AgentConfig.from_env() — agent-runtime settings only.""" @@ -109,9 +114,9 @@ def test_defaults(self): def test_boolean_env_vars(self): env = { - "AGENTSPAN_DAEMON_WORKERS": "false", - "AGENTSPAN_INTEGRATIONS_AUTO_REGISTER": "true", - "AGENTSPAN_STREAMING_ENABLED": "no", + "CONDUCTOR_AGENT_DAEMON_WORKERS": "false", + "CONDUCTOR_AGENT_INTEGRATIONS_AUTO_REGISTER": "true", + "CONDUCTOR_AGENT_STREAMING_ENABLED": "no", } with mock.patch.dict(os.environ, env, clear=True): config = AgentConfig.from_env() @@ -121,8 +126,8 @@ def test_boolean_env_vars(self): def test_numeric_env_vars(self): env = { - "AGENTSPAN_WORKER_POLL_INTERVAL": "250", - "AGENTSPAN_WORKER_THREADS": "4", + "CONDUCTOR_AGENT_WORKER_POLL_INTERVAL": "250", + "CONDUCTOR_AGENT_WORKER_THREADS": "4", } with mock.patch.dict(os.environ, env, clear=True): config = AgentConfig.from_env() @@ -133,7 +138,6 @@ def test_direct_construction(self): config = AgentConfig(worker_thread_count=8) assert config.worker_thread_count == 8 - class TestLivenessConfig: """Liveness monitor settings (used by stateful runs).""" @@ -146,9 +150,9 @@ def test_defaults(self): def test_from_env(self): env = { - "AGENTSPAN_LIVENESS_ENABLED": "false", - "AGENTSPAN_LIVENESS_STALL_SECONDS": "45", - "AGENTSPAN_LIVENESS_CHECK_INTERVAL_SECONDS": "5", + "CONDUCTOR_AGENT_LIVENESS_ENABLED": "false", + "CONDUCTOR_AGENT_LIVENESS_STALL_SECONDS": "45", + "CONDUCTOR_AGENT_LIVENESS_CHECK_INTERVAL_SECONDS": "5", } with mock.patch.dict(os.environ, env, clear=True): config = AgentConfig.from_env() @@ -180,8 +184,8 @@ def test_conductor_log_level_env(self): with mock.patch.dict(os.environ, {"CONDUCTOR_LOG_LEVEL": "WARNING"}, clear=True): assert Configuration().log_level == logging.WARNING - def test_agentspan_log_level_env_fallback(self): - with mock.patch.dict(os.environ, {"AGENTSPAN_LOG_LEVEL": "DEBUG"}, clear=True): + def test_conductor_log_level_env(self): + with mock.patch.dict(os.environ, {"CONDUCTOR_LOG_LEVEL": "DEBUG"}, clear=True): assert Configuration().log_level == logging.DEBUG def test_applied_to_logger_by_runtime(self): diff --git a/tests/unit/ai/test_config_serializer.py b/tests/unit/ai/test_config_serializer.py index 404427f0f..56097d6ba 100644 --- a/tests/unit/ai/test_config_serializer.py +++ b/tests/unit/ai/test_config_serializer.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for AgentConfigSerializer.""" from unittest.mock import MagicMock diff --git a/tests/unit/ai/test_context_passing.py b/tests/unit/ai/test_context_passing.py index b2b23a74a..4b1d4a17b 100644 --- a/tests/unit/ai/test_context_passing.py +++ b/tests/unit/ai/test_context_passing.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for context passing through runtime methods.""" from unittest.mock import MagicMock, patch diff --git a/tests/unit/ai/test_credential_injection_integration.py b/tests/unit/ai/test_credential_injection_integration.py index 198bd6607..186495aaf 100644 --- a/tests/unit/ai/test_credential_injection_integration.py +++ b/tests/unit/ai/test_credential_injection_integration.py @@ -100,7 +100,7 @@ def test_extracted_tool_receives_credential_in_environ(self): # This is what _register_framework_workers does: tool_func = workers[0].func - tool_func._agentspan_framework_callable = True + tool_func._conductor_agent_framework_callable = True worker_fn = make_tool_worker( tool_func, workers[0].name, @@ -130,7 +130,7 @@ def test_extracted_tool_without_credentials_sees_empty_env(self): _, workers = serialize_agent(graph) tool_func = workers[0].func - tool_func._agentspan_framework_callable = True + tool_func._conductor_agent_framework_callable = True # No credential_names, no _workflow_credentials entry with _workflow_credentials_lock: diff --git a/tests/unit/ai/test_deploy_serve.py b/tests/unit/ai/test_deploy_serve.py index 0dca47074..f96129322 100644 --- a/tests/unit/ai/test_deploy_serve.py +++ b/tests/unit/ai/test_deploy_serve.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for AgentRuntime.deploy(), serve(), and run/start/stream by name.""" import pytest diff --git a/tests/unit/ai/test_discovery.py b/tests/unit/ai/test_discovery.py index fbed872af..0b3b68b0d 100644 --- a/tests/unit/ai/test_discovery.py +++ b/tests/unit/ai/test_discovery.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for conductor.ai.agents.runtime.discovery.""" import sys diff --git a/tests/unit/ai/test_dispatch.py b/tests/unit/ai/test_dispatch.py index bf26627cc..0c790649a 100644 --- a/tests/unit/ai/test_dispatch.py +++ b/tests/unit/ai/test_dispatch.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for the dispatch module workers. Tests cover the native-FC workers: check_approval_worker and make_tool_worker. diff --git a/tests/unit/ai/test_dispatch_advanced.py b/tests/unit/ai/test_dispatch_advanced.py index 207b09df0..c546011ec 100644 --- a/tests/unit/ai/test_dispatch_advanced.py +++ b/tests/unit/ai/test_dispatch_advanced.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for advanced dispatch features — circuit breaker, make_tool_worker, and ToolContext injection. """ @@ -254,7 +251,7 @@ def test_framework_callable_gets_object_like_ctx_and_agent(self): def dynamic_instructions(ctx, agent) -> str: return f"{agent.metadata.role}:{ctx.metadata.user.name}:{ctx.prompt}" - dynamic_instructions._agentspan_framework_callable = True + dynamic_instructions._conductor_agent_framework_callable = True wrapper = make_tool_worker(dynamic_instructions, "dynamic_instructions") task = _make_task( @@ -286,7 +283,7 @@ def model_dump(self): def check_output_safety(output): return GuardrailOutput() - check_output_safety._agentspan_framework_callable = True + check_output_safety._conductor_agent_framework_callable = True wrapper = make_tool_worker(check_output_safety, "check_output_safety") result = wrapper(_make_task(input_data={"output": "bad"})) diff --git a/tests/unit/ai/test_example_109_replan_loop.py b/tests/unit/ai/test_example_109_replan_loop.py index e20d10384..78efc91b0 100644 --- a/tests/unit/ai/test_example_109_replan_loop.py +++ b/tests/unit/ai/test_example_109_replan_loop.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the plan-execute-replan loop scaffolding in example 109. These pin the pure-function invariants of the loop (initial plan shape, diff --git a/tests/unit/ai/test_example_110_solve_loop.py b/tests/unit/ai/test_example_110_solve_loop.py index b4648afa9..9aefb6692 100644 --- a/tests/unit/ai/test_example_110_solve_loop.py +++ b/tests/unit/ai/test_example_110_solve_loop.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Agentspan +# Copyright (c) 2025 Conductor OSS # Licensed under the MIT License. See LICENSE file in the project root for details. """Unit tests for the goal-seeking plan-execute-replan loop in example 110. @@ -25,7 +25,7 @@ def _load_example(): def test_evaluate_one_passes_a_known_good_sentence(): ex = _load_example() - # Exactly 20 words, starts with "Agentspan", contains all three keywords. + # Exactly 25 words, starts with "Agentspan", and contains every keyword. s = ( "Agentspan reliably compiles each deterministic loop, iteratively validated through feedback " "and refinement until the orchestrated outcome converges to a stable, predictable, observable system state today." @@ -78,7 +78,7 @@ def test_evaluate_one_keyword_check_requires_whole_word(): ex = _load_example() # Contains 'looping' but not the standalone 'loop'. s = ( - "Agentspan keeps looping through deterministic feedback for " + "Conductor keeps looping through deterministic feedback for " "fifteen consecutive minutes until the task is finally finished today." ) ev = ex.evaluate_one(s) diff --git a/tests/unit/ai/test_example_111_binsearch.py b/tests/unit/ai/test_example_111_binsearch.py index 5a612ec9b..d3e44ad90 100644 --- a/tests/unit/ai/test_example_111_binsearch.py +++ b/tests/unit/ai/test_example_111_binsearch.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the binary-search plan-execute-replan loop in example 111. Pins the pure-function invariants — guess parsing, bounds derivation diff --git a/tests/unit/ai/test_example_113_aml.py b/tests/unit/ai/test_example_113_aml.py index 139dc7d4f..321d2d06e 100644 --- a/tests/unit/ai/test_example_113_aml.py +++ b/tests/unit/ai/test_example_113_aml.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the AML investigation loop in example 113. Pins the pure-function invariants — tool stubs return wrapped ``{"result": diff --git a/tests/unit/ai/test_example_114_rebalance.py b/tests/unit/ai/test_example_114_rebalance.py index 545ede274..9796b003e 100644 --- a/tests/unit/ai/test_example_114_rebalance.py +++ b/tests/unit/ai/test_example_114_rebalance.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the portfolio-rebalance loop in example 114. Pins the deterministic constraint engine (wash-sale, concentration, drift) diff --git a/tests/unit/ai/test_ext.py b/tests/unit/ai/test_ext.py index 8511138ed..521595a84 100644 --- a/tests/unit/ai/test_ext.py +++ b/tests/unit/ai/test_ext.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for extended agent types — GPTAssistantAgent.""" from unittest.mock import MagicMock, patch diff --git a/tests/unit/ai/test_framework_detection.py b/tests/unit/ai/test_framework_detection.py index 7ddbb199e..ec4c18f3c 100644 --- a/tests/unit/ai/test_framework_detection.py +++ b/tests/unit/ai/test_framework_detection.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for LangGraph/LangChain framework auto-detection in serializer.py.""" import pytest from unittest.mock import MagicMock @@ -41,7 +38,7 @@ def test_openai_agent_still_detected(): def test_native_agent_returns_none(): from conductor.ai.agents.frameworks.serializer import detect_framework - # A plain MagicMock with agentspan module but not an isinstance(obj, Agent) + # A plain MagicMock with conductor_agent module but not an isinstance(obj, Agent) # The module prefix "conductor.ai.agents.agent" doesn't match any _FRAMEWORK_DETECTION prefix obj = MagicMock() type(obj).__name__ = "Agent" diff --git a/tests/unit/ai/test_guardrail.py b/tests/unit/ai/test_guardrail.py index 639fd778c..679f8eaac 100644 --- a/tests/unit/ai/test_guardrail.py +++ b/tests/unit/ai/test_guardrail.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for Guardrail, GuardrailResult, RegexGuardrail, LLMGuardrail, OnFail, Position, @guardrail decorator, and external guardrails.""" diff --git a/tests/unit/ai/test_hybrid_transfer_workers.py b/tests/unit/ai/test_hybrid_transfer_workers.py index 07df0764a..1dcf6f14e 100644 --- a/tests/unit/ai/test_hybrid_transfer_workers.py +++ b/tests/unit/ai/test_hybrid_transfer_workers.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for hybrid agent transfer worker registration (issue #38). A hybrid agent has both tools[] AND agents[] with HANDOFF strategy. diff --git a/tests/unit/ai/test_integration_setup.py b/tests/unit/ai/test_integration_setup.py index 1226856ba..c623b60a3 100644 --- a/tests/unit/ai/test_integration_setup.py +++ b/tests/unit/ai/test_integration_setup.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for LLM integration auto-registration.""" from __future__ import annotations @@ -375,7 +372,7 @@ def test_default_is_false(self): def test_env_reads_flag(self): from conductor.ai.agents.runtime.config import AgentConfig - with patch.dict("os.environ", {"AGENTSPAN_INTEGRATIONS_AUTO_REGISTER": "true"}): + with patch.dict("os.environ", {"CONDUCTOR_AGENT_INTEGRATIONS_AUTO_REGISTER": "true"}): config = AgentConfig.from_env() assert config.auto_register_integrations is True @@ -390,6 +387,6 @@ def test_various_truthy_values(self): from conductor.ai.agents.runtime.config import AgentConfig for val in ("true", "True", "TRUE", "1", "yes"): - with patch.dict("os.environ", {"AGENTSPAN_INTEGRATIONS_AUTO_REGISTER": val}): + with patch.dict("os.environ", {"CONDUCTOR_AGENT_INTEGRATIONS_AUTO_REGISTER": val}): config = AgentConfig.from_env() assert config.auto_register_integrations is True, f"Failed for {val!r}" diff --git a/tests/unit/ai/test_kitchen_sink.py b/tests/unit/ai/test_kitchen_sink.py index dd4e0fe4f..3dd169d0d 100644 --- a/tests/unit/ai/test_kitchen_sink.py +++ b/tests/unit/ai/test_kitchen_sink.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Kitchen Sink test suite — structural + behavioral assertions. Tests the kitchen sink structure using direct imports (no server required) diff --git a/tests/unit/ai/test_langchain_executor_example.py b/tests/unit/ai/test_langchain_executor_example.py index cd905fc2e..f79d835f0 100644 --- a/tests/unit/ai/test_langchain_executor_example.py +++ b/tests/unit/ai/test_langchain_executor_example.py @@ -52,7 +52,7 @@ def test_worker_returns_executor_output(self, agent_executor): assert result.output_data["result"] == "42" def test_worker_injects_callback_handler(self, agent_executor): - """Verify that AgentspanCallbackHandler is passed to executor.invoke.""" + """Verify that ConductorAgentCallbackHandler is passed to executor.invoke.""" from conductor.ai.agents.frameworks.langchain import make_langchain_worker, _get_callback_handler_class task = MagicMock() diff --git a/tests/unit/ai/test_mcp_discovery.py b/tests/unit/ai/test_mcp_discovery.py index 23f997efa..3b82f6871 100644 --- a/tests/unit/ai/test_mcp_discovery.py +++ b/tests/unit/ai/test_mcp_discovery.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for MCP tool discovery, expansion, and caching.""" from unittest.mock import MagicMock, patch diff --git a/tests/unit/ai/test_memory.py b/tests/unit/ai/test_memory.py index 8c522ed5d..18f8e5421 100644 --- a/tests/unit/ai/test_memory.py +++ b/tests/unit/ai/test_memory.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for ConversationMemory.""" from conductor.ai.agents.memory import ConversationMemory diff --git a/tests/unit/ai/test_new_features.py b/tests/unit/ai/test_new_features.py index 74b6b9f71..b7c993714 100644 --- a/tests/unit/ai/test_new_features.py +++ b/tests/unit/ai/test_new_features.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for all new gap-closing features. Tests code executors, swarm strategy, semantic memory, OTel tracing, diff --git a/tests/unit/ai/test_normalize_handoff_target.py b/tests/unit/ai/test_normalize_handoff_target.py index c5a3025fb..f060fd1a5 100644 --- a/tests/unit/ai/test_normalize_handoff_target.py +++ b/tests/unit/ai/test_normalize_handoff_target.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for _normalize_handoff_target in runtime.py.""" from conductor.ai.agents.runtime.runtime import _normalize_handoff_target diff --git a/tests/unit/ai/test_ocg.py b/tests/unit/ai/test_ocg.py index c5bba4a21..106eea9f9 100644 --- a/tests/unit/ai/test_ocg.py +++ b/tests/unit/ai/test_ocg.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the OCG (Open Context Graph) retrieval sub-agent factories.""" import pytest diff --git a/tests/unit/ai/test_plan_dataclass_determinism.py b/tests/unit/ai/test_plan_dataclass_determinism.py index 6014753e0..796e66733 100644 --- a/tests/unit/ai/test_plan_dataclass_determinism.py +++ b/tests/unit/ai/test_plan_dataclass_determinism.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Determinism tests for the PLAN_EXECUTE typed-Plan path. Together with the Java-side ``testCompileIsDeterministicAcrossInvocations`` diff --git a/tests/unit/ai/test_planner_context.py b/tests/unit/ai/test_planner_context.py index f17461510..e69588241 100644 --- a/tests/unit/ai/test_planner_context.py +++ b/tests/unit/ai/test_planner_context.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for ``Context`` + ``Agent.planner_context`` wiring. Pure dataclass + serialiser tests — no LLM, no server. Validates: diff --git a/tests/unit/ai/test_result.py b/tests/unit/ai/test_result.py index a8628c0ee..792256702 100644 --- a/tests/unit/ai/test_result.py +++ b/tests/unit/ai/test_result.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for result types.""" from unittest.mock import MagicMock diff --git a/tests/unit/ai/test_resume.py b/tests/unit/ai/test_resume.py index d04d504b6..185631fc2 100644 --- a/tests/unit/ai/test_resume.py +++ b/tests/unit/ai/test_resume.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for AgentRuntime.resume() and resume_async().""" from unittest.mock import AsyncMock, MagicMock, patch diff --git a/tests/unit/ai/test_run.py b/tests/unit/ai/test_run.py index fa60673a0..b5ee7ffe1 100644 --- a/tests/unit/ai/test_run.py +++ b/tests/unit/ai/test_run.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the run.py convenience API.""" import sys diff --git a/tests/unit/ai/test_runtime.py b/tests/unit/ai/test_runtime.py index b9f2d0385..c43532dab 100644 --- a/tests/unit/ai/test_runtime.py +++ b/tests/unit/ai/test_runtime.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the AgentRuntime. Tests runtime helper methods (extract_output, extract_handoff_result, etc.) @@ -218,13 +215,12 @@ class TestAgentRuntimeInit: AgentConfig settings for runtime behaviour only.""" def test_no_args_falls_back_to_env(self): - """AgentRuntime() resolves the server via Configuration's env fallback - (CONDUCTOR_SERVER_URL → AGENTSPAN_SERVER_URL).""" + """AgentRuntime() resolves the server from ``CONDUCTOR_SERVER_URL``.""" import os - keys = ["CONDUCTOR_SERVER_URL", "AGENTSPAN_SERVER_URL"] + keys = ["CONDUCTOR_SERVER_URL"] env_backup = {k: os.environ.pop(k, None) for k in keys} - os.environ["AGENTSPAN_SERVER_URL"] = "http://env-server/api" + os.environ["CONDUCTOR_SERVER_URL"] = "http://env-server/api" try: with patch("conductor.client.orkes_clients.OrkesClients"): @@ -308,7 +304,7 @@ def test_env_override(self): from conductor.ai.agents.runtime.config import AgentConfig with patch.dict( - "os.environ", {"AGENTSPAN_WORKER_THREADS": "4"}, clear=True + "os.environ", {"CONDUCTOR_AGENT_WORKER_THREADS": "4"}, clear=True ): config = AgentConfig.from_env() assert config.worker_thread_count == 4 @@ -1867,6 +1863,16 @@ def test_start_via_server_omits_idempotency_key_when_none(self, runtime): payload = runtime._agent_client.start_agent.call_args[0][0] assert "idempotencyKey" not in payload + def test_start_via_server_keeps_credentials_out_of_payload(self, runtime): + """Credential names configure workers locally and are not workflow input.""" + agent = Agent(name="test", model="openai/gpt-4o") + runtime._agent_client.start_agent = MagicMock(return_value={"executionId": "wf-1"}) + + runtime._start_via_server(agent, "hi", credentials=["OPENAI_API_KEY"]) + + payload = runtime._agent_client.start_agent.call_args[0][0] + assert "credentials" not in payload + class TestStartFrameworkViaServer: """Test _start_framework_via_server() sends correct framework payloads.""" @@ -1881,8 +1887,8 @@ def runtime(self): config = AgentConfig() return AgentRuntime(settings=config) - def test_start_framework_via_server_passes_credentials(self, runtime): - """Framework start payload includes request-level credentials.""" + def test_start_framework_via_server_keeps_credentials_out_of_payload(self, runtime): + """Credential names configure workers locally and are not workflow input.""" runtime._agent_client.start_agent = MagicMock(return_value={"executionId": "wf-fw-1"}) runtime._start_framework_via_server( framework="openai", @@ -1892,7 +1898,7 @@ def test_start_framework_via_server_passes_credentials(self, runtime): ) payload = runtime._agent_client.start_agent.call_args[0][0] - assert payload["credentials"] == ["OPENAI_API_KEY"] + assert "credentials" not in payload def test_start_framework_via_server_passes_context(self, runtime): """Framework start payload includes context.""" diff --git a/tests/unit/ai/test_runtime_metadata_registration.py b/tests/unit/ai/test_runtime_metadata_registration.py index e3fc31c6e..16ec9e144 100644 --- a/tests/unit/ai/test_runtime_metadata_registration.py +++ b/tests/unit/ai/test_runtime_metadata_registration.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """The SDK must stamp a credential-requiring worker's declared secret names onto TaskDef.runtimeMetadata at registration, so the host resolves them at poll time and the SDK's overwrite-registration does not wipe the value.""" diff --git a/tests/unit/ai/test_runtime_server_compile.py b/tests/unit/ai/test_runtime_server_compile.py index 206ab0c68..88952aee2 100644 --- a/tests/unit/ai/test_runtime_server_compile.py +++ b/tests/unit/ai/test_runtime_server_compile.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for server-side compilation in AgentRuntime.""" diff --git a/tests/unit/ai/test_schedule.py b/tests/unit/ai/test_schedule.py index c41ae65ab..1d334f12d 100644 --- a/tests/unit/ai/test_schedule.py +++ b/tests/unit/ai/test_schedule.py @@ -1,6 +1,3 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the schedule module. Covers: diff --git a/tests/unit/ai/test_schema_utils.py b/tests/unit/ai/test_schema_utils.py index 9583f1e29..f57a699f5 100644 --- a/tests/unit/ai/test_schema_utils.py +++ b/tests/unit/ai/test_schema_utils.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for schema_utils — JSON Schema generation from type hints and Pydantic models.""" from typing import Any, Dict, List, Optional, Union diff --git a/tests/unit/ai/test_server_liveness_monitor.py b/tests/unit/ai/test_server_liveness_monitor.py index 1456032ff..dc47bc39e 100644 --- a/tests/unit/ai/test_server_liveness_monitor.py +++ b/tests/unit/ai/test_server_liveness_monitor.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for ServerLivenessMonitor.""" import threading diff --git a/tests/unit/ai/test_signals.py b/tests/unit/ai/test_signals.py index e53ddd897..c37133c4e 100644 --- a/tests/unit/ai/test_signals.py +++ b/tests/unit/ai/test_signals.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for deterministic stop signal and signal methods.""" from unittest.mock import AsyncMock, MagicMock, call, patch diff --git a/tests/unit/ai/test_skill.py b/tests/unit/ai/test_skill.py index 9b27564dc..a6d33709a 100644 --- a/tests/unit/ai/test_skill.py +++ b/tests/unit/ai/test_skill.py @@ -249,8 +249,8 @@ def test_script_worker_executes(self): agent = skill(FIXTURES / "script-skill", model="openai/gpt-4o") workers = create_skill_workers(agent) script_worker = next(w for w in workers if "hello" in w.name) - result = script_worker.func(command="Agentspan") - assert "Hello, Agentspan!" in result + result = script_worker.func(command="Conductor Agents") + assert "Hello, Conductor Agents!" in result def test_no_workers_for_instruction_only_skill(self): from conductor.ai.agents.skill import create_skill_workers, skill diff --git a/tests/unit/ai/test_sse_client.py b/tests/unit/ai/test_sse_client.py index 75f58ddae..e93bd5f7b 100644 --- a/tests/unit/ai/test_sse_client.py +++ b/tests/unit/ai/test_sse_client.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tier 2: Mock SSE server tests for _stream_sse(). Spins up a real HTTP server in a thread that speaks SSE protocol, @@ -416,9 +413,9 @@ def _isolate_ambient_auth(self, monkeypatch): for k in ( "CONDUCTOR_AUTH_KEY", "CONDUCTOR_AUTH_SECRET", - "AGENTSPAN_AUTH_KEY", - "AGENTSPAN_AUTH_SECRET", - "AGENTSPAN_API_KEY", + "CONDUCTOR_AGENT_AUTH_KEY", + "CONDUCTOR_AGENT_AUTH_SECRET", + "CONDUCTOR_AGENT_API_KEY", ): monkeypatch.delenv(k, raising=False) diff --git a/tests/unit/ai/test_sse_parsing.py b/tests/unit/ai/test_sse_parsing.py index 64ef2689c..3d8e34172 100644 --- a/tests/unit/ai/test_sse_parsing.py +++ b/tests/unit/ai/test_sse_parsing.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tier 1: Unit tests for SSE parsing — _parse_sse() and _sse_to_agent_event(). Tests the static methods on AgentRuntime that parse SSE wire format diff --git a/tests/unit/ai/test_termination.py b/tests/unit/ai/test_termination.py index f1c8b59aa..a7999833b 100644 --- a/tests/unit/ai/test_termination.py +++ b/tests/unit/ai/test_termination.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for termination conditions.""" import pytest diff --git a/tests/unit/ai/test_testing_assertions.py b/tests/unit/ai/test_testing_assertions.py index f0cf7dbfc..42349d429 100644 --- a/tests/unit/ai/test_testing_assertions.py +++ b/tests/unit/ai/test_testing_assertions.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for conductor.ai.agents.testing.assertions.""" import pytest diff --git a/tests/unit/ai/test_testing_eval_runner.py b/tests/unit/ai/test_testing_eval_runner.py index 5929c3fd5..86fd3ee73 100644 --- a/tests/unit/ai/test_testing_eval_runner.py +++ b/tests/unit/ai/test_testing_eval_runner.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for conductor.ai.agents.testing.eval_runner.""" from conductor.ai.agents.result import AgentEvent, AgentResult, EventType diff --git a/tests/unit/ai/test_testing_expect.py b/tests/unit/ai/test_testing_expect.py index 0a77c4791..8f65dcc33 100644 --- a/tests/unit/ai/test_testing_expect.py +++ b/tests/unit/ai/test_testing_expect.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for conductor.ai.agents.testing.expect (fluent API).""" import pytest diff --git a/tests/unit/ai/test_testing_mock.py b/tests/unit/ai/test_testing_mock.py index ad02c4983..bfd429066 100644 --- a/tests/unit/ai/test_testing_mock.py +++ b/tests/unit/ai/test_testing_mock.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for conductor.ai.agents.testing.mock.""" from conductor.ai.agents.result import EventType diff --git a/tests/unit/ai/test_testing_recording.py b/tests/unit/ai/test_testing_recording.py index 42e8e5f93..63db24aca 100644 --- a/tests/unit/ai/test_testing_recording.py +++ b/tests/unit/ai/test_testing_recording.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for conductor.ai.agents.testing.recording.""" import json diff --git a/tests/unit/ai/test_testing_strategy_validators.py b/tests/unit/ai/test_testing_strategy_validators.py index 91eb03ef4..38ee0004d 100644 --- a/tests/unit/ai/test_testing_strategy_validators.py +++ b/tests/unit/ai/test_testing_strategy_validators.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for conductor.ai.agents.testing.strategy_validators.""" import pytest diff --git a/tests/unit/ai/test_token_utils.py b/tests/unit/ai/test_token_utils.py index def1486e6..7012cbe72 100644 --- a/tests/unit/ai/test_token_utils.py +++ b/tests/unit/ai/test_token_utils.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the JWT-expiry decoder. Token minting/caching moved to the SDK ``ApiClient`` — see ``test_agent_http.py`` diff --git a/tests/unit/ai/test_tool.py b/tests/unit/ai/test_tool.py index 36cd5d5d1..6e31b026d 100644 --- a/tests/unit/ai/test_tool.py +++ b/tests/unit/ai/test_tool.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the @tool decorator and tool utilities.""" from unittest import mock diff --git a/tests/unit/ai/test_tracing.py b/tests/unit/ai/test_tracing.py index ce4b9f68f..447e0430f 100644 --- a/tests/unit/ai/test_tracing.py +++ b/tests/unit/ai/test_tracing.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for OpenTelemetry tracing instrumentation.""" from unittest.mock import MagicMock diff --git a/tests/unit/ai/test_worker_entries.py b/tests/unit/ai/test_worker_entries.py index af2b189f3..7e9f38fab 100644 --- a/tests/unit/ai/test_worker_entries.py +++ b/tests/unit/ai/test_worker_entries.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """FunctionRef / SpawnSafetyError / probe / async-detection tests (idea-5 Stage 1). Cross-process cases use the real 'spawn' context regardless of platform diff --git a/tests/unit/ai/test_worker_manager.py b/tests/unit/ai/test_worker_manager.py index 11fa1e3cd..b2d955f7b 100644 --- a/tests/unit/ai/test_worker_manager.py +++ b/tests/unit/ai/test_worker_manager.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for WorkerManager.""" from unittest.mock import MagicMock, patch diff --git a/tests/unit/orkes/test_orkes_agent_client.py b/tests/unit/orkes/test_orkes_agent_client.py index c2294703c..2ec6f4618 100644 --- a/tests/unit/orkes/test_orkes_agent_client.py +++ b/tests/unit/orkes/test_orkes_agent_client.py @@ -1,6 +1,3 @@ -# Copyright (c) 2025 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Tests for OrkesAgentClient — the /agent/* control-plane client. Uses a real in-process HTTP server (no mocks, per repo test policy) that emulates diff --git a/tests/unit/orkes/test_scheduler_domain_surface.py b/tests/unit/orkes/test_scheduler_domain_surface.py index 65b65c4f9..8a950e562 100644 --- a/tests/unit/orkes/test_scheduler_domain_surface.py +++ b/tests/unit/orkes/test_scheduler_domain_surface.py @@ -1,6 +1,3 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Unit tests for the schedule-lifecycle surface. The six domain methods (pause/resume/delete/run_now/preview_next/reconcile) diff --git a/tests/unit/orkes/test_scheduler_resource_contract.py b/tests/unit/orkes/test_scheduler_resource_contract.py index bf31b50ed..b399a22d7 100644 --- a/tests/unit/orkes/test_scheduler_resource_contract.py +++ b/tests/unit/orkes/test_scheduler_resource_contract.py @@ -1,6 +1,3 @@ -# Copyright (c) 2026 Agentspan -# Licensed under the MIT License. See LICENSE file in the project root for details. - """Regeneration guards for the HAND-FIXes in scheduler_resource_api.py, plus the OrkesSchedulerClient contract they enable. diff --git a/tests/unit/test_documentation_links.py b/tests/unit/test_documentation_links.py new file mode 100644 index 000000000..a41483ef8 --- /dev/null +++ b/tests/unit/test_documentation_links.py @@ -0,0 +1,71 @@ +"""Validate maintained documentation links and GitHub-style anchors.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +LINK_RE = re.compile(r"(?<!!)\[[^]]*\]\(([^)]+)\)") +HEADING_RE = re.compile(r"^#{1,6}\s+(.+?)\s*#*\s*$", re.MULTILINE) + +CORE_GUIDES = ( + "api-map", "compatibility", "connection-authentication", "core-quickstart", + "debugging", "deployment-scaling", "documentation-parity", "documentation-standard", + "examples", "observability", "reliability", "schedules-events", "schema-client", + "security", "server-setup", "upgrading", "workers", "workflow-lifecycle", + "workflow-testing", "workflows", +) +CANONICAL_DOCS = [ + ROOT / "README.md", + ROOT / "docs" / "README.md", + *[ROOT / "docs" / f"{name}.md" for name in CORE_GUIDES], + *sorted((ROOT / "docs" / "agents").rglob("*.md")), + ROOT / "examples" / "agents" / "README.md", + ROOT / "examples" / "agents" / "adk" / "README.md", + ROOT / "examples" / "agents" / "langgraph" / "README.md", + ROOT / "examples" / "agents" / "openai" / "README.md", +] + + +def _slug(heading: str) -> str: + heading = re.sub(r"`", "", heading).lower() + heading = re.sub(r"[^\w\s-]", "", heading) + return re.sub(r"[-\s]+", "-", heading).strip("-") + + +def _anchors(document: Path) -> set[str]: + counts: dict[str, int] = {} + anchors: set[str] = set() + for heading in HEADING_RE.findall(document.read_text()): + slug = _slug(heading) + count = counts.get(slug, 0) + counts[slug] = count + 1 + anchors.add(slug if count == 0 else f"{slug}-{count}") + return anchors + + +def _links(document: Path): + for target in LINK_RE.findall(document.read_text()): + target = target.strip().strip("<>") + if not target or "://" in target or target.startswith(("mailto:", "#")): + yield target + continue + yield target + + +def test_curated_documentation_links_and_anchors_exist(): + failures = [] + for document in CANONICAL_DOCS: + assert document.exists(), document + for target in _links(document): + if not target or "://" in target or target.startswith("mailto:"): + continue + path_text, _, anchor = target.partition("#") + target_document = document if not path_text else (document.parent / path_text).resolve() + if not target_document.exists(): + failures.append(f"{document.relative_to(ROOT)} -> missing {path_text}") + elif anchor and target_document.suffix == ".md" and anchor not in _anchors(target_document): + failures.append(f"{document.relative_to(ROOT)} -> missing anchor #{anchor} in {path_text or document.name}") + assert not failures, "Broken documentation links:\n" + "\n".join(failures) diff --git a/tests/unit/test_documentation_quality.py b/tests/unit/test_documentation_quality.py new file mode 100644 index 000000000..9bc5b2652 --- /dev/null +++ b/tests/unit/test_documentation_quality.py @@ -0,0 +1,71 @@ +"""Guard the README shape and public Conductor-agent documentation terminology.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +README_SECTIONS = [ + "Choose your path", + "Choose your Conductor server", + "Why Conductor?", + "Requirements and compatibility", + "Install the SDK", + "AI agent quickstart", + "Workflow and worker quickstart", + "Common tasks", + "Troubleshooting", + "Support and project policies", + "License", +] +PRIMARY_GUIDES = [ + "docs/agents/concepts/agents.md", + "docs/agents/concepts/tools.md", + "docs/agents/concepts/multi-agent.md", + "docs/agents/concepts/guardrails.md", + "docs/agents/concepts/deploy-serve-run.md", + "docs/agents/concepts/streaming-hitl.md", + "docs/agents/reference/runtime.md", + "docs/agents/reference/client.md", +] + + +def test_readme_mirrors_the_java_sdk_information_architecture(): + headings = re.findall(r"^## (.+)$", (ROOT / "README.md").read_text(), re.MULTILINE) + positions = [headings.index(section) for section in README_SECTIONS] + assert positions == sorted(positions) + readme = (ROOT / "README.md").read_text() + assert "conductor-oss/python-sdk" in readme + assert "CONDUCTOR_AGENT_LLM_MODEL" in readme + assert "AGENT" + "SPAN" not in readme + + +def test_primary_agent_guides_include_navigation_and_outcome_sections(): + for relative_path in PRIMARY_GUIDES: + content = (ROOT / relative_path).read_text() + assert "## Prerequisites" in content, relative_path + assert "## Expected result" in content or "## Expected result and" in content, relative_path + assert "## Next steps" in content or "## Cleanup and next steps" in content or "## Expected result and next steps" in content, relative_path + + +def test_public_documentation_and_examples_do_not_use_legacy_branding(): + paths = [ROOT / "README.md", *[p for p in (ROOT / "docs").rglob("*.md") if "docs/design" not in str(p)]] + paths.extend((ROOT / "examples" / "agents").rglob("*.md")) + paths.extend((ROOT / "examples" / "agents").rglob("*.py")) + intentional_legacy_branding_fixtures = { + ROOT / "examples" / "agents" / "110_plan_execute_replan_solve.py", + } + violations = [] + for path in paths: + # This example deliberately validates the word "Agentspan" as part of + # its solver input; it is not public product branding. + if path in intentional_legacy_branding_fixtures: + continue + for number, line in enumerate(path.read_text().splitlines(), start=1): + if "copyright" in line.lower(): + continue + if re.search("agent" + "span|agent" + r"\s+" + "span", line, re.IGNORECASE): + violations.append(f"{path.relative_to(ROOT)}:{number}: {line.strip()}") + assert not violations, "Legacy branding remains:\n" + "\n".join(violations) diff --git a/tests/unit/test_pytest_plugin_metadata.py b/tests/unit/test_pytest_plugin_metadata.py new file mode 100644 index 000000000..b4c1f71ad --- /dev/null +++ b/tests/unit/test_pytest_plugin_metadata.py @@ -0,0 +1,17 @@ +"""Ensure pytest auto-loads the Conductor-agent plugin exactly once.""" + +from __future__ import annotations + +import tomllib +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_agent_pytest_plugin_has_one_canonical_entry_point(): + pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text()) + plugins = pyproject["tool"]["poetry"]["plugins"]["pytest11"] + assert plugins == { + "conductor-agents-testing": "conductor.ai.agents.testing.pytest_plugin" + }