Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/agent-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ concurrency:
cancel-in-progress: true

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

jobs:
agent-e2e:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ __pycache__/
*.py[cod]
*$py.class

# Example-run artifacts (generated by run_all_examples.py / coding-agent examples)
examples/agents/run_report.html
examples/agents/codingexamples/

# C extensions
*.so

Expand Down
102 changes: 90 additions & 12 deletions AGENTS.md → AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,33 @@ This guide provides a complete blueprint for creating or refactoring Conductor S

```
┌─────────────────────────────────────────────────┐
│ Application Layer
│ (User's Application Code)
│ Application Layer │
│ (User's Application Code) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ High-Level Clients
│ (OrkesClients, WorkflowExecutor, Workers)
│ High-Level Clients │
│ (OrkesClients, WorkflowExecutor, Workers) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Domain-Specific Clients
│ (TaskClient, WorkflowClient, SecretClient...)
│ Domain-Specific Clients │
│ (TaskClient, WorkflowClient, SecretClient...) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Orkes Implementations
│ (OrkesTaskClient, OrkesWorkflowClient...)
│ Orkes Implementations │
│ (OrkesTaskClient, OrkesWorkflowClient...) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Resource API Layer
│ (TaskResourceApi, WorkflowResourceApi...)
│ Resource API Layer │
│ (TaskResourceApi, WorkflowResourceApi...) │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ HTTP/API Client
│ (ApiClient, HTTP Transport)
│ HTTP/API Client │
│ (ApiClient, HTTP Transport) │
└─────────────────────────────────────────────────┘
```

Expand Down Expand Up @@ -255,6 +255,84 @@ Abstract Interface (20+ methods):

---

## 🤖 Agent SDK Layer

An **optional higher-level layer** for building LLM agents on top of the client +
worker SDK. Agents are authored in code but **compiled and executed on the server**
as durable Conductor workflows; an agent's tools are ordinary Conductor **workers**
(reuse the entire Worker Framework, Phase 3). Full language-agnostic spec:
[`docs/design/WORKER_SDK_IMPLEMENTATION_GUIDE.md` §25](docs/design/WORKER_SDK_IMPLEMENTATION_GUIDE.md#25-agent-sdk-layer-building-agents-on-the-worker-sdk).

### Two Planes

```
┌───────────────────────────────┐
│ AgentRuntime │ (facade)
└───────────────────────────────┘
│ │
control plane │ │ worker plane
▼ ▼
┌───────────────────────┐ ┌────────────────────────────┐
│ AgentClient │ │ WorkerManager/TaskHandler │
│ /agent/* over the │ │ (Phase 3 workers serve │
│ standard ApiClient │ │ the agent's tools) │
└───────────────────────┘ └────────────────────────────┘
```

### Core Components

| Component | Role |
|---|---|
| `AgentRuntime` | User-facing facade: `run`/`start`/`stream`/`deploy`/`serve`/`plan` (+ async). Composes a `Configuration`, `AgentConfig`, `AgentClient`, and a `WorkerManager`. |
| `AgentClient` (interface) + `OrkesAgentClient` | Control plane (compile/deploy/start/status/stream/respond/stop/signal). Built on the **standard `ApiClient`** — reuses its token management; no separate token cache. Same interface + Orkes-impl pattern as the domain clients (Phase 2). |
| `AgentConfig` | Agent-runtime **behaviour** only (worker pool, liveness, streaming, integration auto-register). Connection/auth/log level come from `Configuration`. |
| `RunSettings` | Per-run LLM overrides (`model`/`temperature`/`max_tokens`/…) for a single `run`/`start`; applied to the serialized agent config before start. |
| Framework adapters | Run agents authored in LangChain / LangGraph / OpenAI Agents SDK / Claude Agent SDK on the durable runtime as spawn-safe passthrough workers. |

### Verb Contract

| Method | Blocks | Returns | Starts workers | Registers on server |
|---|---|---|---|---|
| `run` | yes | result (output + ids) | yes | via start |
| `start` | no | handle (`execution_id`) | yes | via start |
| `deploy` | yes | deployment info | no | yes |
| `serve` | yes (until signal) | — | yes | yes (`serve` = `deploy` + serve) |
| `plan` | yes | workflow def | no | no |

### Key Principles

- **Single token authority** — control plane *and* worker-side agent posts reuse the one `ApiClient`'s mint/cache/TTL-refresh/401-retry; never a parallel token cache. In spawned workers, rebuild + cache a client per `(server_url, auth_key)`.
- **Spawn-safety** — tool/worker callables must be module-level (importable/picklable), never `<locals>` closures; entry scripts use a main-module guard.
- **Credentials** — declared per tool; the server resolves + delivers them on wire-only `Task.runtimeMetadata`; injected into the worker env for the call, never read from ambient env.
- **Config single-source** — connection/auth/log level live on `Configuration`; `AgentConfig` is behaviour-only.

### Package Additions

```
src/conductor/
├── client/
│ ├── agent_client.{ext} # AgentClient interface
│ └── orkes/orkes_agent_client.{ext} # OrkesAgentClient (on ApiClient)
└── ai/agents/ # agent authoring + runtime
├── agent.{ext}, run_settings.{ext}
├── runtime/ (runtime, config, worker_manager)
├── _internal/agent_http.{ext} # single token authority for /agent/* posts
└── frameworks/ (langchain, langgraph, claude_agent_sdk, …)
```

### Agent-Layer Checklist

- [ ] `AgentClient` interface + Orkes impl on the standard `ApiClient` (sync + async; SSE reuses the client's auth header)
- [ ] `AgentRuntime` facade with the exact verb contract above
- [ ] `AgentConfig` behaviour-only; connection/auth/log level from `Configuration`
- [ ] `RunSettings` per-run LLM overrides
- [ ] Single token authority across control plane + worker-side posts
- [ ] Tool workers reuse the Worker Framework (Phase 3); credentials via `runtimeMetadata`
- [ ] Framework adapters as spawn-safe passthrough workers
- [ ] Liveness monitor for stateful runs

---

## 🌐 Language-Specific Implementation

### Java Implementation
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def train_model(dataset_id: str) -> dict:
return {'model_id': model.id, 'accuracy': model.accuracy}
```

Disabled by default. Enable per-worker via decorator, constructor, or environment variable (`conductor_worker_<task>_lease_extend_enabled=true`). See [LEASE_EXTENSION.md](LEASE_EXTENSION.md) for the full guide.
Disabled by default. Enable per-worker via decorator, constructor, or environment variable (`conductor_worker_<task>_lease_extend_enabled=true`). See [LEASE_EXTENSION.md](docs/LEASE_EXTENSION.md) for the full guide.

### Monitoring with Metrics

Expand Down Expand Up @@ -494,7 +494,7 @@ End-to-end examples covering all APIs for each domain:
| [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](LEASE_EXTENSION.md) | Automatic heartbeat for long-running tasks |
| [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 |
Expand Down
2 changes: 1 addition & 1 deletion LEASE_EXTENSION.md → docs/LEASE_EXTENSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ If a heartbeat API call fails, the SDK retries up to 3 times with backoff (`1s`,

## Example

See [examples/lease_extension_example.py](examples/lease_extension_example.py) for a complete runnable example that:
See [examples/lease_extension_example.py](../examples/lease_extension_example.py) for a complete runnable example that:
- Defines a long-running worker with `lease_extend_enabled=True`
- Creates a workflow with a short `responseTimeoutSeconds`
- Runs the workflow and proves the task completes despite sleeping longer than the timeout
2 changes: 1 addition & 1 deletion docs/WORKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ class SimpleCppWorker(WorkerInterface):

## Long-Running Tasks and Lease Extension

For tasks that take longer than the configured `responseTimeoutSeconds`, the SDK provides automatic lease extension to prevent timeouts. See the comprehensive [Lease Extension Guide](../LEASE_EXTENSION.md) for:
For tasks that take longer than the configured `responseTimeoutSeconds`, the SDK provides automatic lease extension to prevent timeouts. See the comprehensive [Lease Extension Guide](LEASE_EXTENSION.md) for:

- How lease extension works
- Automatic vs manual control
Expand Down
4 changes: 2 additions & 2 deletions docs/agents/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Agentspan Python SDK
# 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.

Long-running, dynamic plan-execute, and event-driven AI agents in Python. You write plain Python; Agentspan 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.
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.

```python
from conductor.ai.agents import Agent, AgentRuntime
Expand Down
42 changes: 27 additions & 15 deletions docs/agents/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,45 @@
## Runtime init and config

`AgentRuntime` is the entry point. Use it as a context manager so workers shut down
cleanly. Config comes from `AgentConfig.from_env()` by default, or pass overrides.
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 (AGENTSPAN_SERVER_URL etc.)
# From env (CONDUCTOR_SERVER_URL → AGENTSPAN_SERVER_URL, CONDUCTOR_AUTH_*)
with AgentRuntime() as runtime:
runtime.run(agent, "hi")

# Explicit kwargs
with AgentRuntime(server_url="https://prod:8080/api",
api_key="...") as runtime:
# Explicit Configuration
with AgentRuntime(Configuration(server_api_url="https://prod:8080/api")) as runtime:
...

# Or an AgentConfig
config = AgentConfig.from_env()
config.auto_start_server = False
with AgentRuntime(config=config) 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)).
The Conductor `Configuration` object underneath is built from `server_url` and the
auth fields (`api_key`, or `auth_key`/`auth_secret`).
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

ag.configure(server_url="https://prod:8080/api", auto_start_server=False) # before first run
# 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
```
Expand All @@ -61,13 +66,16 @@ ag.shutdown() # explicit cleanup; not required for simple scripts
| `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 execution |
| `runtime.serve(*agents)` | yes (blocks) | — | Long-lived worker process; polls until interrupted |
| `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:
Expand All @@ -88,9 +96,13 @@ runtime.deploy(agent)
# agentspan deploy --path ./agents --agents greeter,support

# Long-lived worker process:
runtime.serve(agent) # blocks, polling for tool tasks
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):

Expand Down
Loading
Loading