whiteducksoftware / whiteducksoftware/flock

[FEATURE] First-class Microsoft Foundry hosted agents through reusable Flock execution primitives

Open
#455 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Python
Stars
120
Forks
14
Avg merge
19h 32m
Merged PRs (30d)
8

Description

Goal

Make a Flock application deployable as a Microsoft Foundry hosted agent, without rewriting its agents, typed artifacts, subscriptions, or blackboard orchestration.

Build this as two independently useful layers:

  • Flock core: transport-independent execution, incremental results, lifecycle management, isolation, and explicit application input/output contracts.
  • Optional flock-foundry integration: Foundry's Responses protocol, request/context mapping, Azure identity integration, and deployment guidance.

The same core primitives must also work in a user-owned ASGI service, queue worker, or another agent runtime without importing Azure packages. Foundry hosting must not require using a Foundry-hosted model: hosting and model-provider selection remain separate concerns.

Problem and existing capabilities

Flock already supports inbound REST endpoints and server components, outbound integrations such as OpenClaw, artifact lifecycle hooks, and condition-based waiting. The missing feature is a supported way to embed a complete Flock application behind an external runtime's request and lifecycle contract.

Today an integration must assemble request validation, execution boundaries, output selection, cancellation, conversation handling, and protocol translation itself. A wrapper around publish() followed by run_until_idle() is insufficient for a long-lived, concurrent host.

Reuse the existing architecture rather than introducing another orchestrator:

Existing Flock capability Required extension or constraint
publish(), run_until(), Until.exists(..., correlation_id=...) A managed execution boundary; condition filtering alone does not isolate or cancel execution.
OrchestratorComponent.on_artifact_published A supported, lifecycle-safe incremental subscription API, not a new independent event bus.
ServerComponent and serve() Retain Flock-hosted HTTP support; make application execution usable without starting a second server.
Context providers and visibility policies Apply trusted, execution-local identity and scope consistently; correlation IDs alone are not authorization.
In-memory, SQLite, and Dapr blackboards Reuse storage abstractions; artifact persistence does not imply scheduler recovery.

Scope and architecture

Foundry gateway
    |
    v
flock-foundry: Responses SDK host + protocol/identity/history mapping
    |
    v
Flock core: application contract + isolated execution + result stream
    |
    v
Existing Flock agents, subscriptions, engines, and blackboard

Other hosts/workers ----------> the same core execution API
Core responsibilities Foundry-only responsibilities
Typed input validation and explicit output allowlist Responses request parsing and output-item/SSE serialization
Run identity, optional session identity, trusted principal context Mapping Foundry response, conversation, user, and sandbox identifiers
Incremental artifact events and terminal outcomes ResponseEventStream lifecycle and protocol errors
Deadlines, cooperative cancellation, bounded shutdown Interpreting SDK cancellation/shutdown signals
Execution-local resources, context, and state boundaries Foundry headers, response storage, and hosting configuration
Generic provider-configuration and telemetry compatibility fixes Azure credential helpers and Foundry deployment examples

Core must not depend on ResponseContext, Azure credentials, Foundry headers, or container protocol versions.

High-level implementation

1. A small, reusable execution API in core

Provide an async execution facade that accepts a typed input, trusted execution context, timeout, and explicit output contract. It must expose an incremental stream and an unambiguous final result. The proposed name below is FlockApplication; final naming should follow maintainer conventions.

Implement it over the existing publisher, scheduler, components, and Until DSL:

  • Register observers before publishing input; emit validated, persisted artifacts incrementally and in observed publication order. Define the observation point relative to component transformations and filtering.
  • Distinguish artifact progress from terminal success, failure, cancellation, and timeout. A WorkflowError, unmet required output, or timeout must not become a successful empty response.
  • Default completion means this execution has finished its runnable cascade, including owned batch flushes within the execution deadline. Passive incomplete joins do not wait forever once no producer remains; missing required outputs fail the run. Open-ended scheduled work requires an explicit stop condition and deadline. Do not equate an empty task set with every workflow being complete.
  • Permit explicit Until conditions, but distinguish reaching a result condition from stopping remaining work. Default execution ownership includes stopping and joining remaining owned work before teardown; any detached continuation must be explicit.
  • Bound per-execution buffering. Backpressure or overflow must be visible and must not silently drop artifacts or block unrelated executions.
  • Handle caller task cancellation, explicit cancellation, iterator closure, and deadlines. Stop scheduling new work for that execution, propagate cancellation to supported engines/tools, and await owned cleanup. Document that thread-based calls and external side effects may not be interruptible.
  • Initialize and close owned resources once. Finishing one request must not close another request's MCP connections, reset its counters, or shut down the host.

Artifact-level streaming is the first milestone. It is not token streaming: an artifact is emitted when available. Fine-grained model/tool progress can build on existing engine events later.

Use this work to harden publish_sync without breaking its existing response schema. A correlation-scoped idle condition needs reliable task/collection accounting; adding an argument to Until.idle() alone is not a complete implementation.

2. Explicit application contract and safe isolation

An application declares its input type or input mapper, public output types, and completion policy. Protocol adapters own protocol-specific rendering.

Never infer public outputs from graph sinks. Blackboard subscriptions can be conditional, cyclic, or extended at runtime. An internal artifact with no current consumer is not automatically safe to expose. Output selection must apply both the application's allowlist and its access policy; internal artifacts and raw errors stay private by default.

Keep these identities separate:

Identity Meaning
Run ID One execution/turn; maps to that run's Flock correlation ID.
Session/conversation ID Optional continuity across turns; never substitutes for a unique run ID.
Principal/partition identity Trusted application access boundary, resolved by the host.

Start with a factory-backed, isolated Flock instance and execution state per run. This preserves existing agent definitions while avoiding a process-wide serialization lock and avoiding assumptions about shared collector, batch, agent, or component state. Initialize immutable type registrations once, not on every request.

A shared-instance optimization is only supported after task ownership, collectors, batches, context, counters, and cleanup are demonstrably scope-aware. Do not mutate a shared agent's tenant_id for each request.

Allocate the execution scope before publication and reject duplicate active run IDs within the application/principal boundary. A transport retry must not accidentally launch the same accepted execution again; recovery/replay is a separate capability.

For shared persistent storage, namespace and authorize reads and writes by application/principal/session/run as appropriate, including context queries and output projection. TenantVisibility is reusable but is agent-identity based; assigning it to input alone does not provide complete per-request isolation. Retain the existing visibility/context-provider enforcement.

Require an explicit history mode:

  • Stateless: each request is independent.
  • Conversation: reconstruct the supported prior conversation messages through the adapter and map them into the typed application input/context. Do not republish historical inputs and rerun their workflows. Serialize overlapping turns only within the same principal/conversation; unrelated conversations remain concurrent.

The initial conversation mode uses platform-managed message history, not durable Python objects or restored blackboard execution state. Applications needing durable domain memory configure it separately. Define bounded execution retention and cleanup so a long-lived host does not accumulate completed runs indefinitely.

3. Optional first-class Foundry adapter

Use the official azure-ai-agentserver-responses hosting SDK, with the stable 2.1.x line as the initial compatibility target and container Responses protocol 2.0.0. Python package versions, container protocol versions, and service API versions are different version axes.

Pin exact sample dependencies and publish the supported SDK/service capability matrix, including regional availability and preview restrictions. A stable Python package does not make all service features generally available; long-running recovery remains separately gated.

The adapter wraps the generic application and delegates HTTP/protocol machinery to ResponsesAgentServerHost rather than reimplementing it.

Foundry contract Adapter behavior
Root POST /responses, GET /readiness, plain HTTP, port 8088/default PORT Use the SDK host and its lifespan. Readiness reflects initialization and draining, not merely an open port.
Nonstreaming and streaming responses Project only allowed outputs into response items; emit valid IDs, sequence/order, and exactly one terminal outcome. Accumulated streaming output must agree with the nonstreaming result.
Stored background and streaming+background Use SDK response storage, polling, and cancellation endpoints. Require store=true; a disconnected background stream must not cancel the execution.
Foreground disconnect and explicit cancellation Translate the SDK signal to cancellation of the corresponding core execution, not a global orchestrator shutdown.
context.shutdown Stop admission and drain for a bounded interval; do not report successful completion for interrupted work. Inspect shutdown alongside cancellation because SDK 2.1.0 foreground shutdown can set both signals.
response_id, conversation identifiers, agent_session_id Map response ID to a unique run; map conversation separately; treat the sandbox session ID as a third concept. Do not assume conversation_chain_id is always stable across linked turns.
platform_context.user_id_key Treat as an opaque user partition key supplied by the trusted Foundry gateway, not an Entra tenant ID. Resolve it through an explicit application identity policy.
x-agent-foundry-call-id Propagate unchanged to relevant Foundry service calls; it is not an authentication, tenant, or conversation key.
Unsupported request features Reject unsupported content/tools/options explicitly, before executing; never silently drop input or claim capabilities the adapter does not implement.

The initial adapter supports text input and text-message conversations, typed Flock outputs rendered as text or JSON text, and all four response modes above. Arbitrary multimodal input, client-driven function-tool round trips, and steerable execution are not implicitly included.

Offer an ASGI application plus a standalone .run() convenience entrypoint. The standalone SDK host is the reference deployment. Externally serving it must preserve SDK lifespan and shutdown handling; dashboard mounting is a separate composition task, not a prerequisite or a one-line guarantee. Do not expose Flock's administrative/dashboard API publicly by default.

Provide an explicit local-development identity mode. Production must not accept arbitrary identity headers from direct callers or fall back to one shared principal when required identity is absent.

4. Azure model configuration and dependency compatibility

Keep Azure inference configuration in the optional integration or existing provider integration, not in the execution contract. Provide a documented managed-identity path for supported DSPy/LiteLLM Azure models, with renewable credentials, explicit endpoint/deployment configuration, and the correct token audience. Distinguish the hosted agent's identity from project/infrastructure identities and document their required role assignments.

Do not require API keys, persist access tokens, or rely on a one-time token that expires. A helper should return engine/provider configuration rather than assume Flock accepts arbitrary lm_kwargs.

Address two reusable compatibility prerequisites:

  1. Telemetry dependencies: Flock currently pins OpenTelemetry 1.34.1; AgentServer core 2.1.0 requires >=1.43.0,<2.0.0. Establish a tested compatible dependency range, keep the OpenTelemetry package family aligned, and remove, replace, or make optional the incompatible legacy Jaeger exporter path. Preserve working tracing and allow a single host-owned SDK/exporter configuration.
  2. Output-token configuration: make a bounded completion-token setting reachable through DSPyEngine for affected models. Resolve the correct parameter through the supported DSPy/LiteLLM provider path, without sending conflicting limits or silently removing the cap. max_tokens incompatibility is model/API-specific, not a property of all Foundry models.

Recommend a separately versioned flock-foundry distribution. Its dependency must specify the first compatible flock-core release; a separate package does not solve incompatible dependencies in the same Python environment. Core-only installation must not pull in AgentServer. The repository layout/release pipeline may be a sibling package or a separate repository; that packaging choice must not move protocol code into core.

Examples

The new application and adapter APIs below are proposed, not available in released Flock. Agent declarations use Flock's existing style. The names illustrate the intended developer experience rather than freeze implementation signatures.

Define a reusable Flock application
import os

from pydantic import BaseModel, Field
from flock import Flock, flock_type
from flock.runtime import FlockApplication, RunContext  # proposed


@flock_type
class IncidentRequest(BaseModel):
    report: str
    history: list[dict[str, str]] = Field(default_factory=list)


@flock_type
class IncidentSummary(BaseModel):
    summary: str


def build_flock() -> Flock:
    flock = Flock(os.environ["DEFAULT_MODEL"], no_output=True)
    flock.agent("triage").consumes(IncidentRequest).publishes(IncidentSummary)
    return flock


application = FlockApplication(
    factory=build_flock,
    input_type=IncidentRequest,
    output_types=(IncidentSummary,),
    required_output_types=(IncidentSummary,),
)

The factory creates execution-local state. More agents and internal artifact types can be added without changing the host; they are not exposed unless the output contract explicitly includes them.

Run from a non-Foundry worker
async def handle_job():
    async with application.run_stream(
        IncidentRequest(report="Checkout requests are timing out."),
        context=RunContext(
            run_id="job-42",
            principal_id="customer-a",  # resolved by the trusted worker
            session_id=None,
        ),
        timeout=60,
    ) as execution:
        async for event in execution:
            if event.kind == "artifact":
                print(event.artifact.payload)

        result = await execution.result()
        result.raise_for_status()

The async context owns listener and execution cleanup. Normal stream closure is not a substitute for checking the terminal result. This example has no Azure dependency.

Expose the same application in Foundry
from flock_foundry import FoundryResponsesAdapter  # proposed

host = FoundryResponsesAdapter(
    application,
    history_mode="conversation",
    input_mapper=lambda turn: IncidentRequest(
        report=turn.text,
        history=turn.history,
    ),
    output_mapper=lambda summary: summary.summary,
)

if __name__ == "__main__":
    host.run()

Here turn is the adapter's proposed normalized text-turn representation: text contains the current input and history contains prior supported messages with roles preserved. It excludes the current message to avoid duplication. Each turn still receives a distinct run ID. Use history_mode="stateless" for independent jobs.

The container example must include a reproducible dependency lock, a Linux AMD64 image, the SDK host as the default entrypoint, and deployment configuration declaring Responses protocol 2.0.0. Document project/model configuration, registry access, identity/RBAC, readiness, logs, and shutdown behavior.

For a local smoke request, after explicitly enabling local-development identity mode:

curl http://localhost:8088/responses \
  -H 'Content-Type: application/json' \
  -d '{"input":"Checkout requests are timing out.","stream":true}'

For an already deployed agent, the current azure-ai-projects 2.6.0 client supports an agent-bound OpenAI client:

import os

from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential

with (
    DefaultAzureCredential() as credential,
    AIProjectClient(
        endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
        credential=credential,
    ) as project,
    project.get_openai_client(agent_name="flock-agent") as client,
):
    conversation = client.conversations.create()
    response = client.responses.create(
        input="Checkout requests are timing out.",
        extra_body={"conversation": conversation.id},
    )
    print(response.output_text)

Reuse the conversation ID on a subsequent turn; do not reuse the response ID as the next execution's identity. This client snippet uses existing SDK APIs, unlike the proposed Flock APIs above.

Delivery and acceptance criteria

Deliver as one feature with independently reviewable core and adapter changes:

  1. Reusable core: application contract, isolated execution/lifecycle, artifact stream, and dependency/provider prerequisites. Include a non-Azure worker or ASGI example.
  2. Foundry support: optional adapter, foreground and basic stored-background modes, text conversation mapping, Azure identity guidance, and a deployable sample.
  3. Separately gated follow-up: crash-resumable Flock execution. Do not advertise restart recovery until its distinct acceptance criteria are met.
  • Core installs and runs without Azure/AgentServer dependencies; the adapter installs in a clean environment without dependency overrides.
  • Existing Flock agent definitions, direct invocation, REST, and dashboard behavior remain compatible.
  • Concurrent executions for different principals/conversations progress independently; no process-wide request lock, cross-run outputs, context leakage, or premature cleanup.
  • Delayed multi-stage, fan-out, join, and batch workflows exercise the documented completion rules. A fast unrelated run cannot complete, cancel, or hold open another run.
  • Incremental artifacts arrive before the cascade completes. Slow consumers, observer-registration races, and iterator closure do not silently lose output or leak tasks.
  • Timeout, cancellation, workflow failure, and missing required output produce distinct non-success outcomes. Cancelling one run does not stop another.
  • Foreground JSON/SSE and stored background JSON/SSE agree on outputs and lifecycle. Background work survives stream disconnect within the same host lifetime; polling and cancellation work.
  • Two-turn conversation history preserves roles without rerunning prior inputs. Different users cannot access each other's history, stored responses, blackboard artifacts, or events.
  • Shutdown stops admission, drains within a configured deadline, and leaves interrupted responses in the appropriate non-success state. Without recovery enabled, host loss is documented as non-resumable and stale in_progress responses have a documented reconciliation/operator path.
  • The sample starts on the required port, passes readiness, and runs on Foundry with managed-identity inference and a bounded output-token setting for a supported model.
  • Examples and documentation clearly distinguish artifact streaming, model-token streaming, conversation history, persistent domain data, and execution recovery.

Recovery boundary and non-goals

Basic stored background execution is not a promise of crash recovery. The SDK can persist Responses state; that does not restore Flock's tasks, partial joins/batches, or external tool effects.

The recovery follow-up must define checkpoint/resume or safe replay, task and response identity, completed-step deduplication, output-item/stream replay, and side-effect idempotency. Only then enable resilient_background and handle is_recovery/exit_for_recovery() appropriately. A local SQLite file is durable only to the extent its storage volume is; Dapr also requires a reachable, configured backend. Neither implies a distributed scheduler.

Other non-goals for this issue: implementing Bedrock/A2A/MCP adapters, universal protocol abstractions, Kubernetes deployment tooling, automatic sink discovery, dashboard co-hosting, steerable workflows, and arbitrary model/tool token streaming. The architecture should permit these without claiming they are already supported.

Validation notes and related work

Reviewed on 2026-09-12 against Flock source commit 006923b (pyproject.toml: 0.5.611), published AgentServer 2.1.0 contracts, and current official documentation.

Sources

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No implementation files or tests are named. Start by tracing the existing publisher, scheduler, components, Until DSL, ServerComponent, and serve() entry points, then determine how a FlockApplication boundary can reuse them without Azure dependencies. Done requires isolated execution, incremental artifacts, explicit terminal outcomes, cancellation and cleanup, plus an optional ResponsesAgentServerHost adapter.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, python
Domain
ai, api, backend, cloud
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.