whiteducksoftware / whiteducksoftware/flock
[FEATURE] First-class Microsoft Foundry hosted agents through reusable Flock execution primitives
Nobody has claimed this yet.
- 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-foundryintegration: 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
Untilconditions, 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:
- 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. - Output-token configuration: make a bounded completion-token setting reachable through
DSPyEnginefor affected models. Resolve the correct parameter through the supported DSPy/LiteLLM provider path, without sending conflicting limits or silently removing the cap.max_tokensincompatibility 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:
- Reusable core: application contract, isolated execution/lifecycle, artifact stream, and dependency/provider prerequisites. Include a non-Azure worker or ASGI example.
- Foundry support: optional adapter, foreground and basic stored-background modes, text conversation mapping, Azure identity guidance, and a deployable sample.
- 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_progressresponses 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
- Flock execution and scheduling: orchestrator, scheduler, sync REST service.
- Flock extension and isolation boundaries: publication hooks, server components, context providers, visibility.
- Flock dependencies and engine configuration: pyproject.toml, DSPy engine, signature/payload builder, Dapr limitations.
- Current Foundry service contract: hosted-agent contract, migration to protocol 2.0.0, sessions versus conversations, agent identity.
- Published SDK 2.1.0 behavior: Responses host and storage, shutdown/disconnect handling, chain-ID derivation, 2.1.0 changelog.
- Dependency evidence: AgentServer core 2.1.0 metadata, older 1.0.0b17 metadata, Azure reasoning-model token parameters.
- Recovery and examples: long-running resilience, official resilient-streaming sample, agent-bound client example.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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