Multi-Agent Harness for Visual Design Myli, a provider-neutral, pre-alpha Python harness for agents that propose RFC 6902 changes to application-owned JSON design documents, has been released. The harness never persists or applies returned candidates to application state and is independent of Pydantic, canvas formats, rendering stacks, model providers, ORMs, progress transports, and persistence systems. It includes public protocols for main and vision models, optional LiteLLM implementations, and a domain-neutral AgentTool with capabilities, per-run call budgets, timeouts, and result-size limits. Myli is a provider-neutral, pre-alpha Python harness for agents that propose RFC 6902 changes to application-owned JSON design documents. It never persists or applies the returned candidate to application state. The core is independent of Pydantic, canvas formats, rendering stacks, model providers, ORMs, progress transports, and persistence systems. Read the Myli documentation https://myli.readthedocs.io/en/latest/ for the complete guide and API reference. result = await myli.run request=user request, design=current design, can edit=True, capabilities={"media.transform"}, history=history, on event=handle event, on step=persist step, RunResult contains the user-facing message, optional validated candidate, changed flag, proposed patch, approved run-scoped assets, all tool outcomes, step traces, run ID, and final provider metadata. Successful and unsuccessful tool outcomes are also available as filtered properties. python from typing import Any from myli import DesignSpec, ModelRequest, ModelResponse, Myli def load untrusted document value: Any - dict str, Any : if not isinstance value, dict : raise ValueError "document must be an object" return value design spec = DesignSpec name="design", schema={"type": "object"}, validator=load untrusted document, serializer=lambda document: document, normalizer=normalize trusted stored document, input migrator=migrate stored document, class ApplicationMainModel: async def complete self, request: ModelRequest - ModelResponse: return await application transport.complete request myli = Myli design spec=design spec, main model=ApplicationMainModel , vision model=application vision model, renderer=application renderer, DesignSpec has deliberately separate methods for trusted stored input and untrusted model candidates. Candidate JSON is validated at runtime against the schema and must serialize back without coercion, default insertion, field dropping, or any other silent rewrite. Stored input may opt into migration and normalization before each run. MainModel and VisionModel are public protocols. Applications may inject different transports, endpoints, credentials, or fakes. LiteLLMMainModel and LiteLLMVisionModel are optional implementations: python -m pip install "myli litellm " The LiteLLM main implementation supports structured, JSON, and text output modes, rejects options that override client-owned request fields, and translates provider failures into Myli's stable exception hierarchy. Pydantic remains optional: python from myli.integrations.pydantic import PydanticDesignSpec AgentTool is domain-neutral. A tool declares a JSON input schema, capabilities, per-run call budget, timeout, and result-size limit, then receives a ToolContext with the current run ID, isolated evidence, zero-based model step, run-unique step and tool-batch IDs, and its zero-based position and size within that batch: python async def execute arguments, context : ... Myli validates arguments and results, enforces capabilities and limits, and records a ToolOutcome with one of succeeded, failed, rejected, timed out, or deferred. Calls are sequential by default. Parallel execution requires both the harness option and an explicit parallel safe declaration on every call in the batch. Each tool can select FailureMode.RETURN ERROR or FailureMode.RAISE. ToolMiddleware can allow, reject, or defer work before execution and observe the outcome afterward. Applications can use it for approval, audit, tenancy, ordering, transactions, and rate limiting. The step and batch metadata lets middleware apply a rule exactly once per model work phase, including when a batch executes in parallel. CandidatePolicy runs before rendering a proposal and before returning the final candidate. CandidateContext includes capabilities, approved assets, and all run-scoped outcomes; successful tool outcomes is the authorization-safe subset. Named asset providers return generic Asset values with application-defined kinds and metadata. Myli assigns run-scoped references, detects conflicting provider identities, applies search budgets and result limits, and tracks provenance. An asset can contain a RenderedArtifact preview or an async preview loader. Previews load only after discovery and explicit inspection, are cached per run, and are bounded by timeout, byte-size, and media-type checks. Loading failures are returned to the model and can be retried. Binary artifacts never appear in step trace dictionaries. When a renderer and vision model are configured, render design validates the candidate and application policies before rendering, validates the artifact, and returns visual feedback as a tool result. A changed candidate cannot be rendered while editing is disabled, but the unchanged current design can be rendered diagnostically. Events cover the run, model, tool, and validation lifecycle. They contain run and step IDs, status, safe messages, elapsed time, and an optional tool name, but no arguments or results. Applications can map them to logs, metrics, SSE, WebSockets, or ignore them. StepTrace retains normalized model output, provider-exposed reasoning, tool calls and outcomes, validation failures, response ID, model, finish reason, usage, and latency. It provides to dict and redacted . Async on step and a configurable trace redactor make no persistence assumptions. HarnessLimits bounds model steps, retries, renders, searches, search results, asset inspections, patches, documents, history, artifacts, operation timeouts, and optionally the entire run. Every custom tool retains its own limits. apply json patch implements add, remove, replace, move, copy, and test, including root operations and escaped pointers, against a deep copy. JsonPatchLimits guard operation count, patch bytes, pointer depth, value bytes, document bytes, invalid indexes, non-JSON values, non-finite numbers, child moves, and candidate expansion. CancelledError is never wrapped. Cancellation propagates into model, renderer, vision, search, preview, and custom-tool work. All counters, assets, outcomes, and caches live in local run state, so one Myli instance can safely serve concurrent requests. uv sync uv run ruff format --check . uv run ruff check . python -m unittest discover -s tests -v Myli is distributed under the MIT License.