Table of Contents

πŸ‡«πŸ‡· Version franΓ§aise

See also: Back to index

Known limitations and constraints

  • InMemoryUnitOfWork has, by design, no durable persistence step: aggregates live in the in-memory repositories and SaveChangesAsync boils down to the (real and ordered) dispatch of domain events. A durable adapter would replace this adapter as a whole β€” there is no longer any EF migration promise in the code.
  • Execution state persistence (R3.8): by default, crew execution states (ICrewExecutionStateManager) are in-memory only (no recovery after a crash). Durable persistence is opt-in: register a checkpointing state store (AddOrkeonCheckpointing / AddOrkeonSqliteCheckpointing / AddOrkeonPostgresCheckpointing) and enable AddCrewExecutionStatePersistence(...) (or the Orkeon:ExecutionState:Persistence section). Once enabled, states are persisted at every transition and reloaded/reused after a restart β€” see Opt-in subsystems. v1 limits: metadata persisted as invariant strings, ToolsUsed not persisted.
  • The framework targets .NET 10 β€” current version 1.0.0-rc.4 (defined in src/Directory.Build.props). .NET 11 (GA on 2026-11-10) is not a target yet: ci.yml builds and tests the solution with the 11.x SDK (preview channel, non-blocking until GA) as an early warning, and both dotnet tools (orkeon, orkeon-repl) roll forward to a newer major runtime (RollForward=Major) so a machine with only .NET 11 installed can run them. The target framework, global.json and the container base images move on GA day, not before.
  • The functional scope is frozen at 16 LLM providers (fourteen vendors and two aggregators β€” OpenRouter and Mammouth AI, the one motivated exception to the freeze, integrated documentation-first and awaiting their first campaign), 79 built-in tools and 6 memory stores until real users ask for more: a single maintainer carries the whole surface, and every addition is a permanent cost. No 17th provider: endpoints that speak the OpenAI dialect need no provider (point Orkeon:Llm:BaseUrl at them); tools are written in .ork.ts scripts or plugins. The rule and what remains welcome are in CONTRIBUTING.md.
  • LlmConfig.ApiKeySecretName is reserved, not resolved. The framework never turns a secret name into an API key: every provider guard and every request path reads LlmConfig.ApiKey, so a config carrying only ApiKeySecretName is unconfigured and every call against it answers that an API key is required. The *WithSecret factories (WithDefaultModelSecret, Gpt35TurboWithSecret, ClaudeWithSecret) record the name and nothing more. A host that keeps its keys in a secret store resolves the name itself and assigns the result to ApiKey; the supported paths for supplying it are the settings file (Llm:ApiKey) and the environment (ORKEON_Llm__ApiKey). ApiKey carried an [Obsolete] attribute steering callers to ApiKeySecretName until rc.3; it was removed because it pointed at a path that does not exist.
  • LLM streaming is uniform at the framework boundary: all 16 providers inherit the SSE/NDJSON streaming reader from HttpLlmProviderBase (IStreamingLlmProvider). SupportsStreaming is not an unconditional true: it follows whether the provider is actually usable (an API key when the vendor needs one, plus the resource endpoint for Azure), so an unconfigured provider declares false and every caller branching on the capability takes the buffered path β€” which returns the explicit "API key is required" answer β€” instead of an SSE branch that would end without a single chunk. The remaining per-provider exception is Ollama's /api/chat (see the Ollama entry below)
  • FlowEngine and the Flows system (sequential, parallel, decision steps) exist in the infrastructure but constitute an alternative orchestration system distinct from Crews
  • Deliberately-minimal DI defaults β€” IAgentPlanner (fixed 4-step plan), ITaskDelegator (denies delegation), IKnowledgeStore (in-memory, non-persistent; its SearchAsync ignores the query) and IAgentExecutionService are stand-ins that each announce themselves with a one-time Warning naming the remediation; nothing calls an LLM or persists implicitly. Full inventory and replacement gestures: Default behaviors
  • Custom tools must implement IBaseTool or inherit from ToolBase<TReq,TRes>. Runtime plugin loading exists (opt-in AddOrkeonPlugins(...) β€” directory discovery, isolated collectible AssemblyLoadContexts, see Plugins); v1 limits: loaded assemblies run with full trust (no sandbox), and there is no plugin manifest or hot-reload
  • Several subsystems (A2A, monitoring backend, NIST, DLP, tool rate-limiting, key rotation, benchmarking, multi-modal validation, kickoff hooks, RAG subsystem since RAG-01/C6 β€” AddOrkeonRag + AddOrkeonRagTools since RAG-02, plus the RAG opt-ins AddOrkeonOnnxReranker since RAG-04 and the AddOrkeonRagWebFallback transport since RAG-06) are opt-in: they are no longer registered by AddOrkeonInfrastructure()/AddOrkeonApplication() and are enabled through their dedicated extension β€” see Opt-in subsystems
  • A2A β€” task persistence is opt-in (PUB-08): register a checkpointing state store plus AddOrkeonA2ATaskPersistence(), and GET /a2a/tasks/{id} answers 200 with the recorded lifecycle (404 for unknown ids) while DELETE records the cancellation (404 for unknown ids; cancellation stays advisory β€” in-flight work is not interrupted). Without the opt-in, GET keeps returning an explicit 501 rather than fabricating state. The broader v1.0 spec gaps (bindings, push notifications, 9-state lifecycle, A2A-Version) are inventoried in the A2A conformance matrix.
  • A2A mTLS β€” A2AClient loads the client certificate (A2ASecurityOptions.ClientCertificatePath) and always applies full server-certificate validation β€” there is deliberately no accept-any-certificate opt-out; a self-signed or local-dev server pins its CA via TrustedCertificateAuthorities. Server side, when RequireMutualTls = true, A2AServer only accepts client certificates that chain to one of the TrustedCertificateAuthorities or match a pinned TrustedClientCertificateThumbprints entry (403 otherwise) and refuses to start when no trust anchor is configured (fail-closed). Certificate revocation is not checked (private CAs without CRL/OCSP endpoints are assumed). Client-side CA pinning only vouches for an unknown chain β€” a host-name mismatch is never bypassed. Requests without an allowed authentication scheme are rejected (401) when AllowedAuthSchemes is set. Real mutual TLS termination requires an HTTPS binding of HttpListener (HTTP.sys reservation / server certificate) configured at the host level; the discovery endpoint /.well-known/agent.json remains public.
  • Semantic agent selection (OrkeonApplicationOptions.AgentSelectionStrategy = Embedding) inherits the real embedding resolution chain since RAG-02/C5: the hash-based SimpleEmbeddingService has been removed, and the default IEmbeddingService adapts the IEmbeddingProvider port (local BGE when AddOrkeonLocalEmbeddings() is registered β†’ remote provider from Orkeon:Embeddings β†’ fail-fast InvalidOperationException at first use with an actionable message). There is no silent non-semantic fallback anymore: without any embedding provider the Embedding strategy fails loudly β€” either configure one, or prefer the Skill strategy (lexical matching, no dependency) or the FirstFit default (first available agent)
  • Fixed since RAG-01, re-based in RAG-02 β€” the RAG path no longer degrades silently: the ingestion pipeline (Orkeon.Rag) embeds chunks at ingestion and the document store searches by scored vector similarity (similarity scores survive end-to-end to ScoredChunk), and the IEmbeddingProvider port resolves as local BGE provider (when AddOrkeonLocalEmbeddings() is registered) β†’ remote provider from Orkeon:Embeddings β†’ fail-fast (InvalidOperationException on first use with an actionable message). HashBasedEmbeddingProvider is a test double only β€” it is never resolved implicitly
  • Native tool calling β€” Ollama (lifted in LLM-07): OllamaLlmProvider now reaches /api/chat β€” and is wired with the native OpenAI strategy β€” whenever the conversation declares tools, replays tool calls, or carries an image. It reshapes Ollama's response (message.tool_calls with arguments as a JSON object, no choices array) into the OpenAI body the framework's single parser reads (choices[0].message.tool_calls, arguments as a JSON string), and assigns positional call ids since Ollama issues none. Remaining limits: everything else β€” plain conversations, single-prompt generation, streaming β€” still uses /api/generate (NDJSON streaming and GBNF grammar are only available there), so the text fallback protocol stays in charge for models without tool support; /api/chat streaming is not implemented, so a streamed call keeps the prompt-completion path; and images must be supplied as bytes, since Ollama takes bare base64 payloads and never fetches a remote URL.
  • RAG adaptive/corrective profiles β€” the LLM-dependent nodes degrade offline: since RAG-06 the Adaptive-RAG Iterative route delegates to the corrective graph pipeline (the RAG-05 documented fallback to quality is lifted; the route step traces delegate=corrective and RagTrace.Route carries the decision). Remaining honest limits: the default complexity classifier is the deterministic heuristic; the llm classifier requires a registered IChatClient (without one it falls back to the heuristic with a warning); and without a real LLM the corrective graph cannot bridge a vocabulary gap β€” in --offline evaluation the extractive stub feeds rewrite_query/evaluate/check_groundedness: the grader's tolerant parser fishes grade words out of the echoed passages (pseudo-random verdicts; the groundedness fallback stays safely grounded), and a triggered rewrite degenerates to the stub's fixed prefix β€” one meaningless probe shared by every case β€” so the offline corrective numbers measure the loop's guard rails plus that degradation, not rewrite quality, and can land below quality (measured table and analysis in examples/rag/eval/README.md; the end-to-end mechanism β€” evaluator verdict β†’ rewrite β†’ recovered citation on the seeded q-007 case β€” is proven by CorrectiveRagMechanismSlowTests with a scripted LLM, labelled as such). Same offline note as before for the LLM-backed query transformers (multi-query/rag-fusion/hyde): the measured offline path is QueryTransform.Mode=none + heuristic routing.
  • RAG profiles balanced/quality/adaptive require the ONNX reranker packages: their presets enable the cross-encoder rerank stage, and the profile resolver fails fast at the first query (actionable InvalidOperationException) when Orkeon.Rag.Onnx + Orkeon.Rag.Onnx.Model are not referenced and AddOrkeonOnnxReranker() is not registered. fast (the default) and corrective (the graph corrects by looping, no linear rerank stage) work without the ONNX packages. adaptive needs them because its SingleShot route delegates to balanced.
  • ONNX native runtime β€” the suites that load it: two test suites load the ONNX Runtime native library β€” tests/tools/Orkeon.Tools.Embeddings.Local.Tests (local BGE-micro-v2 embeddings) and tests/rag/Orkeon.Rag.Onnx.Tests (ms-marco cross-encoder reranker) β€” so they need a platform where that library loads. Until 2026-09-07 this entry also described a SIGSEGV (exit 139) that took the process down after the Embeddings.Local tests passed, called it a teardown artefact of the native runtime (PUB-17 / SONAR-14), and had ci.yml and publish.yml tolerate exactly that shape. It was not a teardown artefact. Dispose_Releases_Embedder called EmbedBatchAsync on a disposed provider, and the provider β€” which set a _disposed flag it never read β€” dereferenced the freed native session: undefined behaviour that surfaced as a bogus OnnxRuntimeException on a warm heap and as a process-killing SIGSEGV on a dirty one, sometimes before a single result had been reported. The tolerance could not have caught that either, since the accounting it compared came from the crashed process itself. LocalEmbeddingProvider now throws ObjectDisposedException from EmbedBatchAsync and Dimensions; the suite runs its 11 tests and exits 0 (17 consecutive runs), the two CI steps are plain dotnet test runs again, and integration.yml no longer excludes the project. Both suites were also measured green inside a container on 2026-09-07, so the earlier instruction to run them only on a host or in CI is dropped.
  • Orkeon.Tools.Embeddings.Local rests on a pre-release of an archived upstream β€” the local on-device embeddings opt-in is built on SmartComponents.LocalEmbeddings 0.1.0-preview10148 (pinned in Directory.Packages.props), which is the only version that exists: upstream has never cut a stable release, and the repository the package's projectUrl points at (dotnet-smartcomponents/smartcomponents) is archived β€” the code moved to dotnet/smartcomponents, which still ships no 1.0. Waiting for a stable upstream is not a plan. The licence side is settled and documented (MIT for both the wrapper and the bundled BGE-micro-v2 weights, provenance established by hand in THIRD-PARTY-NOTICES.md sections 1-2); what remains is a packaging constraint: NuGet refuses to pack a stable package that depends on a pre-release (NU5104). So a stable 1.0.0 of Orkeon cannot carry this dependency inside its main package β€” which is precisely why this opt-in ships as its own Orkeon.Tools.Embeddings.Local package instead of being folded into the Orkeon umbrella (PUB-25). The consequence for a consumer: referencing this opt-in pulls a prerelease dependency into your own graph, and if you then publish a stable package of your own that depends on it, you hit NU5104 in turn. Escape hatches, cheapest first: (a) do not reference the opt-in β€” the IEmbeddingProvider port then resolves a remote provider from Orkeon:Embeddings (see the semantic agent-selection entry above); (b) implement IEmbeddingProvider over your own embedding stack; (c) vendor the upstream inference wrapper (MIT, so this is allowed) into an assembly you control and drop the package reference. The orkeon / orkeon-repl dotnet tools and the installers are not affected by NU5104 β€” a tool package is self-contained and its own version is what NuGet checks. Decision (2026-09-11): the pre-release is assumed, not vendored. Orkeon.Tools.Embeddings.Local will ship as a stable 1.0.0 that depends on SmartComponents.LocalEmbeddings 0.1.0-preview10148; NU5104 is silenced in that one packaging project (the umbrella, Orkeon.Tools and the CLI never carry the dependency), a stable pack of the whole line succeeds, and the README says in one line which opt-in stays on a pre-release and why. Vendoring the inference wrapper (escape hatch (c)) remains possible for a consumer who needs a fully stable graph; it is not worth a second copy of an MIT wrapper for the framework itself.
  • RAG web fallback is double opt-in and off by default: the corrective graph's web_fallback node runs only when both switches are enabled β€” Orkeon:Rag:Corrective:WebFallback (policy: may the graph leave the local store) and Orkeon:Rag:WebFallback (transport: SearxNG endpoint, AddOrkeonRagWebFallback registers the retriever only when enabled with a non-empty endpoint). Every fetched page is screened by PromptInjectionDocumentValidator before entering the working set (Rejected pages never enter; Suspicious follows the configured policy) β€” see Security
  • MCP β€” dual-era client/server since PUB-07: the client probes with server/discover and speaks the modern stateless revision (2026-07-28 β€” per-request _meta, version renegotiation on UnsupportedProtocolVersionError) or falls back to the legacy initialize handshake (2025-11-25, 2025-06-18, 2024-11-05; 2025-03-26 is deliberately excluded β€” it is the only revision mandating JSON-RPC batching, which this implementation does not speak); the server serves both eras concurrently. Honest gaps of the modern surface: no subscriptions/listen (server-push change notifications), no multi-round-trip requests (an input_required result surfaces as a tool error), no MCP authorization (OAuth); the HTTP transport is the Streamable HTTP JSON-response mode β€” modern headers are sent and an SSE-framed response body is unwrapped to its final message, but server-initiated streams are not consumed. Interop against reference servers (MCP Inspector) has not run yet β€” tracked in PUB-07's closure note.
  • LanceDB β€” LanceDbMemoryProvider now targets a remote LanceDB Cloud/Enterprise server (Lance REST Namespace protocol, Arrow IPC payloads); there is no local JSON store anymore. Remote API limits (details in Memory system): SearchAsync requires an FTS index on content (created automatically at table creation, otherwise the server error is propagated β€” never simulated locally); HybridSearchAsync issues two server requests (_distance/_score rankings server-side) and merges the two ranked lists locally, like the official SDKs' rerankers; since the delete API does not return a counter, DeleteAsync/UpdateAsync perform a prior existence request to preserve their boolean contract; the score conversion = 1 βˆ’ _distance is only meaningful for the cosine metric (default)
  • spawn_agent is not registered by any shipped composition root β€” the class (SpawnAgentTool, budget-controlled self-spawn for the autonomous mode) ships in Orkeon.Infrastructure, but neither orkeon run nor the REPL put it in the tool registry; a host that wants self-spawn registers it explicitly with an IAgentFactory. See docs/tools/inventory.md and docs/orchestration/autonomous.md.
  • Per-execution mount namespaces are entered by orkeon-host only β€” IFileSystemScope / AsyncLocalFileSystemScope (Domain) hand one execution flow its own FileSystemRegistry, and FileSystemService consults that ambient override on every operation, so two crews running in the same process can each address their own /output over different physical folders β€” the registry's duplicate-virtual-path rejection is per instance, never global. orkeon-host is the one shipped composition root that enters a namespace, per hosted crew, from Orkeon:Host:Crews:*:Mounts (CrewRunner). Every other entry point β€” orkeon run, orkeon forge, orkeon rag β€” runs against the boot registry, one flat mount set per process whose virtual paths must be globally unique, which is why they reserve their roots against the settings file rather than scoping them, and why the host still renames the crews it grants no mounts to (/crews, /crews-1, …). A host that wants its own namespace composes it with ScopedMountComposition.ForExecution: entering a scope replaces the boot set rather than merging with it, so the composition carries the boot mounts forward and lets the execution's own mounts shadow them at the paths they claim β€” dropping them takes /llm-logs and /sandbox down with the run, and takes the root the runner loads the crew from. A second, process-wide gate remains either way β€” IPathValidator's allowed roots are captured at boot, so a granted folder outside the workspace root resolves in the namespace and is then refused by the validator; PathSecurity:AdditionalAllowedDirectories is where an operator widens it. See VFS compliance.