docs update, allow some debugging
This commit is contained in:
@@ -14,6 +14,11 @@ on both the current docs and the implementation state.
|
||||
- Make the LLM/client path progressive: inspect sources, create a draft,
|
||||
patch, validate, compile, save, run.
|
||||
- Prefer smaller discovery/inspection responses over one huge payload.
|
||||
- Done: the operator manual now categorizes workflow tools into discovery,
|
||||
draft workspace, stateless draft, artifact/deployment, and raw escape
|
||||
hatch groups.
|
||||
- Next: tighten the actual tool responses around that map so list calls stay
|
||||
compact and inspect/run calls carry the detailed payloads.
|
||||
|
||||
3. **Wrapper creation ergonomics**
|
||||
- Help create workflow-ready wrappers from raw capabilities.
|
||||
@@ -37,6 +42,19 @@ on both the current docs and the implementation state.
|
||||
and failure policy. Do not model this as plain parallel calls over sync
|
||||
handlers.
|
||||
|
||||
Frame stress points to solve before either feature:
|
||||
|
||||
- `RunState.current_frame_id` currently models one active execution cursor.
|
||||
Parallel foreach likely needs multiple runnable child frames.
|
||||
- `ExecutionFrame.metadata` currently carries ad hoc foreach data. Subgraphs and
|
||||
parallel foreach should get typed frame payloads or strongly bounded helper
|
||||
accessors before metadata grows more meanings.
|
||||
- Subgraph frames need child workflow identity/version/deployment binding, not
|
||||
just a generic metadata dictionary.
|
||||
- `RunState.current_node_id` duplicates the current frame's node id for
|
||||
convenience. Any multi-frame scheduler must either keep that as a selected
|
||||
cursor or replace it with an explicit scheduling view.
|
||||
|
||||
## Why This Order
|
||||
|
||||
`wf_core` is now coherent enough for the next bottleneck to be platform and DX:
|
||||
|
||||
@@ -92,6 +92,12 @@ limits and intended adapter seam.
|
||||
- Saved workflow-as-node execution with interrupts requires a core runtime
|
||||
upgrade: nested run state, child-frame trace preservation, interrupt bubbling
|
||||
with path metadata, and resume back into the child workflow.
|
||||
- Frames are currently a serial execution stack. That is enough for root
|
||||
workflow execution, serial foreach, and node-level interrupts, but async
|
||||
parallel foreach and native subgraphs will stress the model. In particular,
|
||||
`RunState.current_frame_id` assumes one active cursor, `ExecutionFrame.metadata`
|
||||
is ad hoc, and subgraph frames will need explicit child workflow/deployment
|
||||
identity.
|
||||
- Runtime errors are still ordinary exceptions plus failed run status. A richer
|
||||
error payload can be added later, but should be designed as part of trace/run
|
||||
state rather than scattered exceptions.
|
||||
|
||||
@@ -392,6 +392,11 @@ Expected shape:
|
||||
}
|
||||
```
|
||||
|
||||
Do not request trace detail by default. `run_deployment` returns `trace_count`
|
||||
as the total original trace length and supports explicit ranged debug reads such
|
||||
as `"trace_range": {"start": 0, "limit": 10}`. Trace entries may contain
|
||||
resolved inputs, outputs, and state changes.
|
||||
|
||||
## 10. Rebind The Same Artifact Later
|
||||
|
||||
If another compatible account appears:
|
||||
@@ -549,3 +554,151 @@ Concrete MCP sequence:
|
||||
`create_wrapper_from_workspace` accepts the same request shape except there is
|
||||
no `kind` field. It always saves `kind="wrapper"` and the result is discoverable
|
||||
as a workflow capability named `workflow.<artifact_id>.v<version>`.
|
||||
|
||||
## Wrapper Happy Path
|
||||
|
||||
Use this path when a raw/provider capability is callable but you want a reusable
|
||||
workflow-facing wrapper with explicit schemas, outcomes, and bindings.
|
||||
|
||||
### 1. Inspect The Source Capability
|
||||
|
||||
```yaml
|
||||
tool: wf.workflow.inspect_capability
|
||||
arguments:
|
||||
{
|
||||
"qualified_name": "demo.personal.echo_tool"
|
||||
}
|
||||
```
|
||||
|
||||
The response includes `wrapper_hints`. These hints are scaffolding, not final
|
||||
business logic. They suggest draft schemas and basic input/output bindings.
|
||||
|
||||
### 2. Create A Draft Workspace From The Capability
|
||||
|
||||
```yaml
|
||||
tool: wf.workflow.create_draft_workspace_from_capability
|
||||
arguments:
|
||||
{
|
||||
"request": {
|
||||
"workspace_id": "echo_wrapper_draft",
|
||||
"capability_name": "demo.personal.echo_tool",
|
||||
"name": "echo_wrapper",
|
||||
"title": "Echo Wrapper Draft"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This creates a mutable, revisioned workspace using the inspected
|
||||
`wrapper_hints`.
|
||||
|
||||
### 3. Patch Or Validate The Workspace
|
||||
|
||||
If the hints are good enough, validate:
|
||||
|
||||
```yaml
|
||||
tool: wf.workflow.validate_draft_workspace
|
||||
arguments:
|
||||
{
|
||||
"request": {
|
||||
"workspace_id": "echo_wrapper_draft"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If one field is wrong, use a focused helper or JSON Patch. For example, change
|
||||
one route:
|
||||
|
||||
```yaml
|
||||
tool: wf.workflow.set_draft_route
|
||||
arguments:
|
||||
{
|
||||
"request": {
|
||||
"workspace_id": "echo_wrapper_draft",
|
||||
"revision": 1,
|
||||
"step_id": "echo",
|
||||
"outcome": "error",
|
||||
"target": "__end__"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Save The Workspace As A Wrapper Artifact
|
||||
|
||||
```yaml
|
||||
tool: wf.workflow.create_wrapper_from_workspace
|
||||
arguments:
|
||||
{
|
||||
"request": {
|
||||
"workspace_id": "echo_wrapper_draft",
|
||||
"artifact_id": "echo_wrapper",
|
||||
"version": 1,
|
||||
"title": "Echo Wrapper",
|
||||
"outcomes": ["ok", "error"],
|
||||
"source_bindings": {
|
||||
"demo": "demo.personal"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The saved wrapper appears in workflow capability discovery as:
|
||||
|
||||
```text
|
||||
workflow.echo_wrapper.v1
|
||||
```
|
||||
|
||||
### 5. Deploy And Test The Wrapper
|
||||
|
||||
Saved wrappers that use logical sources need a deployment binding when called:
|
||||
|
||||
```yaml
|
||||
tool: wf.workflow.save_deployment
|
||||
arguments:
|
||||
{
|
||||
"deployment": {
|
||||
"id": "echo_wrapper.personal",
|
||||
"artifact_id": "echo_wrapper",
|
||||
"artifact_version": 1,
|
||||
"bindings": {
|
||||
"demo": "demo.personal",
|
||||
"wf.std": "wf.std"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then test the wrapper through the workflow-facing REPL tool:
|
||||
|
||||
```yaml
|
||||
tool: wf.workflow.call_capability
|
||||
arguments:
|
||||
{
|
||||
"qualified_name": "workflow.echo_wrapper.v1",
|
||||
"deployment_id": "echo_wrapper.personal",
|
||||
"payload": {
|
||||
"text": "hello"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Expected shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"qualified_name": "workflow.echo_wrapper.v1",
|
||||
"kind": "wrapper_artifact",
|
||||
"outcome": "completed",
|
||||
"output": {
|
||||
"echoed": "hello"
|
||||
},
|
||||
"diagnostics": []
|
||||
}
|
||||
```
|
||||
|
||||
Current limitation: saved wrappers are executed through the deployment runtime,
|
||||
so `call_capability` reports the wrapper run status (`completed`, `failed`, or
|
||||
`interrupted`) rather than remapping inner node outcomes. True graph-as-node
|
||||
outcome propagation belongs in `wf_core` subgraph support.
|
||||
|
||||
See `examples/mcp_wrapper_authoring_flow.py` for this same sequence through the
|
||||
Python handler layer.
|
||||
|
||||
@@ -97,6 +97,96 @@ Typical tools:
|
||||
- `wf.workflow.validate_deployment`
|
||||
- `wf.workflow.run_deployment`
|
||||
|
||||
## Workflow Tool Map
|
||||
|
||||
The workflow surface is intentionally split by job. Use the primary path first;
|
||||
the advanced tools exist for debugging, compatibility, or focused repair.
|
||||
|
||||
### Discovery
|
||||
|
||||
Primary:
|
||||
|
||||
- `wf.workflow.list_capabilities`: compact, paged workflow capability search.
|
||||
It returns names, source ids, outcomes, and top-level field names.
|
||||
- `wf.workflow.inspect_capability`: full contract for one selected capability,
|
||||
including schemas, outcomes, and wrapper authoring hints.
|
||||
- `wf.workflow.call_capability`: REPL-style direct test of one workflow
|
||||
capability or saved wrapper artifact.
|
||||
|
||||
Supporting:
|
||||
|
||||
- `wf.workflow.list_artifacts`: compact list of saved workflow and wrapper
|
||||
artifacts.
|
||||
- `wf.workflow.inspect_artifact`: full saved artifact payload.
|
||||
|
||||
### Draft Workspaces
|
||||
|
||||
Primary:
|
||||
|
||||
- `wf.workflow.create_draft_workspace_from_capability`: preferred wrapper
|
||||
bootstrap. It inspects one capability, applies its hints, and creates a
|
||||
revisioned draft workspace.
|
||||
- `wf.workflow.get_draft_workspace`: fetch current revision and optionally the
|
||||
full draft document.
|
||||
- `wf.workflow.validate_draft_workspace`: refresh diagnostics without changing
|
||||
revision.
|
||||
- `wf.workflow.create_wrapper_from_workspace`: save a validated draft workspace
|
||||
as a callable wrapper capability.
|
||||
- `wf.workflow.create_artifact_from_workspace`: save a validated draft workspace
|
||||
as a full workflow artifact.
|
||||
|
||||
Focused repair helpers:
|
||||
|
||||
- `wf.workflow.set_draft_name`
|
||||
- `wf.workflow.set_draft_route`
|
||||
- `wf.workflow.set_step_input_map`
|
||||
- `wf.workflow.set_step_output_map`
|
||||
|
||||
These helpers are deliberately narrow. Prefer them over JSON Patch when the
|
||||
caller only needs to edit one common field.
|
||||
|
||||
Advanced workspace tools:
|
||||
|
||||
- `wf.workflow.list_draft_workspaces`: find mutable draft sessions.
|
||||
- `wf.workflow.patch_draft_workspace`: apply RFC 6902 JSON Patch with revision
|
||||
checking.
|
||||
- `wf.workflow.delete_draft_workspace`: cleanup abandoned sessions.
|
||||
- `wf.workflow.create_draft_workspace`: store a caller-provided draft directly.
|
||||
- `wf.workflow.create_minimal_draft_workspace`: bootstrap around one capability
|
||||
when the caller already knows schemas and bindings.
|
||||
|
||||
### Stateless Draft Tools
|
||||
|
||||
Use these when the caller can resend the whole draft on every call:
|
||||
|
||||
- `wf.workflow.validate_draft`
|
||||
- `wf.workflow.compile_draft`
|
||||
- `wf.workflow.patch_draft`
|
||||
- `wf.workflow.create_artifact_from_draft`
|
||||
|
||||
Draft workspaces are usually safer for LLM clients because they avoid a
|
||||
rewrite-the-whole-document loop and preserve optimistic-concurrency revisions.
|
||||
|
||||
### Artifact And Deployment
|
||||
|
||||
Primary:
|
||||
|
||||
- `wf.workflow.save_deployment`: bind one saved artifact version to concrete
|
||||
sources.
|
||||
- `wf.workflow.validate_deployment`: check dependency availability and drift.
|
||||
- `wf.workflow.run_deployment`: execute a saved deployment with input. The
|
||||
default response is compact and returns `trace_count`; pass `trace_range`
|
||||
only when debugging a failed or surprising run.
|
||||
|
||||
Advanced:
|
||||
|
||||
- `wf.workflow.save_artifact`: persist a complete artifact JSON document.
|
||||
- `wf.workflow.create_artifact_from_plan`: raw compiled-plan escape hatch.
|
||||
|
||||
`create_artifact_from_plan` bypasses draft ergonomics. Use it only when the
|
||||
caller already has a trusted compiled raw workflow plan or is deliberately
|
||||
testing the lower-level artifact boundary.
|
||||
|
||||
### `wf.docs`
|
||||
|
||||
Local documentation source.
|
||||
@@ -253,6 +343,15 @@ Use `run_deployment` rather than expecting newly saved workflows to appear as
|
||||
brand-new MCP tools. Many LLM harnesses do not reliably refresh callable tool
|
||||
schemas mid-session.
|
||||
|
||||
The default `run_deployment` response is intentionally compact. It includes run
|
||||
status, output, diagnostics, and `trace_count`, where `trace_count` is the total
|
||||
number of trace entries in the original run. If the caller needs node-level
|
||||
debug detail, pass an explicit `trace_range` object such as
|
||||
`{"start": 0, "limit": 10}`; otherwise trace entries stay out of the normal
|
||||
response. Trace entries may include resolved node inputs, node outputs, and
|
||||
state changes, so treat them as debug payloads rather than ordinary list/summary
|
||||
data.
|
||||
|
||||
## Which Tool Do I Use?
|
||||
|
||||
| I want to... | Use |
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any
|
||||
from wf_artifacts import WorkflowDeployment
|
||||
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
|
||||
|
||||
from mcp_workflow_surface import prepare_demo_service
|
||||
from examples.mcp_workflow_surface import prepare_demo_service
|
||||
|
||||
|
||||
async def author_echo_wrapper_from_capability(root: Path) -> dict[str, Any]:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .handlers import WorkflowSurfaceHandlers
|
||||
from .models import TraceRange
|
||||
from .tools import register_workflow_tools
|
||||
|
||||
__all__ = ["WorkflowSurfaceHandlers", "register_workflow_tools"]
|
||||
__all__ = ["TraceRange", "WorkflowSurfaceHandlers", "register_workflow_tools"]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import asdict
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from wf_artifacts import (
|
||||
@@ -49,6 +50,7 @@ from .constants import (
|
||||
DEFAULT_OK_OUTCOME,
|
||||
RUNTIME_ERROR_CAPABILITY,
|
||||
)
|
||||
from .models import TraceRange
|
||||
from .refs import parse_workflow_surface_capability_id
|
||||
from .wrapper_hints import wrapper_hints_for_capability
|
||||
|
||||
@@ -862,6 +864,7 @@ class WorkflowSurfaceHandlers:
|
||||
*,
|
||||
deployment_id: str,
|
||||
workflow_input: dict[str, Any],
|
||||
trace_range: TraceRange | None = None,
|
||||
) -> dict[str, Any]:
|
||||
deployment, artifact, diagnostics = self._deployment_validation(deployment_id)
|
||||
if diagnostics:
|
||||
@@ -894,6 +897,22 @@ class WorkflowSurfaceHandlers:
|
||||
status=run.status.value,
|
||||
output=run.output,
|
||||
trace_count=len(run.trace),
|
||||
trace=(
|
||||
[
|
||||
asdict(entry)
|
||||
for entry in run.trace[
|
||||
trace_range.start : trace_range.start + trace_range.limit
|
||||
]
|
||||
]
|
||||
if trace_range is not None
|
||||
else None
|
||||
),
|
||||
trace_start=trace_range.start if trace_range is not None else None,
|
||||
trace_limit=trace_range.limit if trace_range is not None else None,
|
||||
trace_truncated=(
|
||||
trace_range is not None
|
||||
and len(run.trace) > trace_range.start + trace_range.limit
|
||||
),
|
||||
)
|
||||
|
||||
def _deployment_validation(
|
||||
@@ -1219,8 +1238,12 @@ def _run_payload(
|
||||
diagnostics: list[DependencyDiagnostic] | None = None,
|
||||
output: dict[str, Any] | None = None,
|
||||
trace_count: int = 0,
|
||||
trace: list[dict[str, Any]] | None = None,
|
||||
trace_start: int | None = None,
|
||||
trace_limit: int | None = None,
|
||||
trace_truncated: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
payload = {
|
||||
"deployment_id": deployment.id,
|
||||
"artifact_id": artifact.id,
|
||||
"artifact_version": artifact.version,
|
||||
@@ -1231,3 +1254,11 @@ def _run_payload(
|
||||
],
|
||||
"trace_count": trace_count,
|
||||
}
|
||||
if trace is not None:
|
||||
# Trace entries can grow quickly, so the public run tool only includes
|
||||
# a bounded debug slice when the caller explicitly asks for a range.
|
||||
payload["trace_start"] = trace_start
|
||||
payload["trace_limit"] = trace_limit
|
||||
payload["trace"] = trace
|
||||
payload["trace_truncated"] = trace_truncated
|
||||
return payload
|
||||
|
||||
@@ -98,6 +98,22 @@ class CallCapabilityResult(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class TraceRange(BaseModel):
|
||||
"""Bounded debug trace slice request for deployment runs."""
|
||||
|
||||
start: int = Field(
|
||||
default=0,
|
||||
ge=0,
|
||||
description="Zero-based trace entry offset to start reading from.",
|
||||
)
|
||||
limit: int = Field(
|
||||
default=25,
|
||||
ge=1,
|
||||
le=100,
|
||||
description="Maximum trace entries to return. Keep this small.",
|
||||
)
|
||||
|
||||
|
||||
class DraftWorkspaceResult(BaseModel):
|
||||
"""Inspector-visible response contract for draft workspace operations."""
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ from .models import (
|
||||
SetDraftRouteRequest,
|
||||
SetStepInputMapRequest,
|
||||
SetStepOutputMapRequest,
|
||||
TraceRange,
|
||||
ValidateDraftWorkspaceRequest,
|
||||
)
|
||||
|
||||
@@ -538,14 +539,17 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
||||
title="Run Workflow Deployment",
|
||||
description=(
|
||||
"Run deployment_id with workflow_input and return status, output, "
|
||||
"diagnostics, and trace_count."
|
||||
"diagnostics, and trace_count. Debug traces can include resolved "
|
||||
"inputs and state changes; pass trace_range only when needed."
|
||||
),
|
||||
)
|
||||
async def run_deployment(
|
||||
deployment_id: str,
|
||||
workflow_input: dict[str, Any],
|
||||
trace_range: TraceRange | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await handlers.run_deployment(
|
||||
deployment_id=deployment_id,
|
||||
workflow_input=workflow_input,
|
||||
trace_range=trace_range,
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ from examples.mcp_workflow_surface import (
|
||||
create_and_run_echo_deployment,
|
||||
prepare_demo_service,
|
||||
)
|
||||
from examples.mcp_wrapper_authoring_flow import author_echo_wrapper_from_capability
|
||||
|
||||
|
||||
def test_mcp_workflow_surface_example_discovers_ok_and_error_outcomes(
|
||||
@@ -35,3 +36,17 @@ def test_mcp_workflow_surface_example_runs_happy_path(tmp_path) -> None:
|
||||
assert payload["status"] == "completed"
|
||||
assert payload["output"]["echoed"] == "hello"
|
||||
assert payload["diagnostics"] == []
|
||||
|
||||
|
||||
def test_mcp_wrapper_authoring_flow_example_creates_and_calls_wrapper(tmp_path) -> (
|
||||
None
|
||||
):
|
||||
payload = asyncio.run(author_echo_wrapper_from_capability(tmp_path))
|
||||
|
||||
assert payload["inspected_hints"]["capability_name"] == "demo.personal.echo_tool"
|
||||
assert payload["workspace"]["status"] == "valid"
|
||||
assert payload["created"]["saved"] is True
|
||||
assert payload["called"]["qualified_name"] == "workflow.echo_wrapper.v1"
|
||||
assert payload["called"]["kind"] == "wrapper_artifact"
|
||||
assert payload["called"]["outcome"] == "completed"
|
||||
assert payload["called"]["output"]["echoed"] == "hello"
|
||||
|
||||
@@ -15,7 +15,7 @@ from wf_authoring import node, reducer
|
||||
from wf_mcp.broker import WfMcpService
|
||||
from wf_mcp.models import ConnectionConfig, RawWorkflowPlan
|
||||
from wf_mcp.storage import FileStore
|
||||
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
|
||||
from wf_mcp.workflow_surface import TraceRange, WorkflowSurfaceHandlers
|
||||
from wf_core.models.steps import InputPathBinding, OutputBinding
|
||||
from wf_core.paths import GraphSourcePath, LocalPath, StatePath
|
||||
from wf_platform import (
|
||||
@@ -906,6 +906,87 @@ def test_workflow_surface_runs_non_interrupting_deployment() -> None:
|
||||
assert payload["status"] == "completed"
|
||||
assert payload["output"]["echoed"] == "hello"
|
||||
assert payload["diagnostics"] == []
|
||||
assert payload["trace_count"] == 1
|
||||
assert "trace" not in payload
|
||||
|
||||
|
||||
def test_workflow_surface_run_deployment_can_include_trace_detail() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_run_trace_detail"
|
||||
)
|
||||
artifact_store.save_artifact(_echo_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
id="echo.personal",
|
||||
artifact_id="echo",
|
||||
artifact_version=1,
|
||||
bindings=[{"logical_source": "demo", "concrete_source": "demo.personal"}],
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_run_trace_detail_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
)
|
||||
service.register_specs("demo.personal", echo_tool)
|
||||
handlers = WorkflowSurfaceHandlers(service)
|
||||
|
||||
payload = asyncio.run(
|
||||
handlers.run_deployment(
|
||||
deployment_id="echo.personal",
|
||||
workflow_input={"text": "hello"},
|
||||
trace_range=TraceRange(start=0, limit=10),
|
||||
)
|
||||
)
|
||||
|
||||
assert payload["status"] == "completed"
|
||||
assert payload["trace_count"] == 1
|
||||
assert payload["trace_start"] == 0
|
||||
assert payload["trace_limit"] == 10
|
||||
assert payload["trace_truncated"] is False
|
||||
assert len(payload["trace"]) == 1
|
||||
assert payload["trace"][0]["node_id"] == "echo"
|
||||
assert payload["trace"][0]["outcome"] == "ok"
|
||||
|
||||
|
||||
def test_workflow_surface_run_deployment_can_read_empty_trace_range() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_run_trace_empty_range"
|
||||
)
|
||||
artifact_store.save_artifact(_echo_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
id="echo.personal",
|
||||
artifact_id="echo",
|
||||
artifact_version=1,
|
||||
bindings=[{"logical_source": "demo", "concrete_source": "demo.personal"}],
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_run_trace_empty_range_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
)
|
||||
service.register_specs("demo.personal", echo_tool)
|
||||
handlers = WorkflowSurfaceHandlers(service)
|
||||
|
||||
payload = asyncio.run(
|
||||
handlers.run_deployment(
|
||||
deployment_id="echo.personal",
|
||||
workflow_input={"text": "hello"},
|
||||
trace_range=TraceRange(start=5, limit=10),
|
||||
)
|
||||
)
|
||||
|
||||
assert payload["trace_count"] == 1
|
||||
assert payload["trace_start"] == 5
|
||||
assert payload["trace_limit"] == 10
|
||||
assert payload["trace"] == []
|
||||
assert payload["trace_truncated"] is False
|
||||
|
||||
|
||||
def test_workflow_surface_runs_deployment_with_bound_node_spec_dependency() -> None:
|
||||
|
||||
Reference in New Issue
Block a user