Agent orchestration¶
API version
Chatbot API: 2.41.7
Oria uses a supervised LangGraph workflow. The main graph combines two control nodes, one supervisor and seven specialist-agent nodes. A dedicated graph is also compiled for every specialist so selected API or MCP entry points can skip the supervisor without bypassing safety and long-term-memory processing.
Graph-specific code is isolated from the rest of the Chatbot application:
src/chatbot/
├── chat/ # REST/SSE chat surface
├── history/ # conversations and messages
├── event/ # feedback-event lifecycle
├── long_term_memory/ # durable preferences API
├── simulation/ # REST/WebSocket simulation surface
├── rag/ # knowledge retrieval and its HTTP/admin lifecycle
└── agentic/
├── graphs.py # main graph and specialist subgraphs
├── agents/ # specialist definitions
├── core/ # graph-internal business processing
├── config/ # agentic configuration
├── middlewares/ # prompts, history and tool policies
├── prompts/ # stable prompt content
├── tools/ # agent tools and handoffs
├── utils/ # graph utilities
├── llms.py # dynamic LLM profiles
└── states.py # graph state and request context
RAG remains alongside agentic because it also owns an HTTP and lifecycle
surface; the Knowledge Base agent consumes it as a dependency.
At the start of a Career Chat turn, a deterministic availability check hides its Knowledge Base handoff when neither enabled indexed RAG nor configured active web sources are available. A bounded tenant-scoped chunk-existence read replaces any model/embedding work for this check; URLs are only fetched after a handoff.
Main graph¶
START -> pre_supervisor_gate
guardrail check
├── block / doubt -> END
├── unavailable / malformed / timeout -> stop with a safe error
└── allow -> memory + supervisor in parallel
-> enabled specialist -> END
└── Career Chat may transfer to Knowledge Base
├── supported answer -> verbatim delivery -> END
└── miss / unavailable -> Career Chat response -> END
Safety approval precedes retrieval, long-term memory and specialist tools.
The gate gives safety classification and any boundary-response generation one
shared 15-second deadline. Errors, provider refusals, malformed decisions and
incomplete generated responses stop the turn; cancellation propagates. Once approved, the memory branch remains separate
from response generation. A grounded RAG answer still permits an update of durable
preferences based on the latest user message, not the generated answer.
JSON chat keeps its response shape and SSE terminates with run.failed when
validation is temporarily unavailable; neither proceeds to model-driven actions.
The HTTP handler projects native LangGraph V3 tasks, tools and custom progress into
versioned, payload-minimizing activities. It reads the final result from the same
owned stream and cancels nested work on disconnect. In SSE mode, the final
specialist middleware privately selects a tool action before a separate tool-free
public generation, when needed. A provider callback streams only this final phase;
postprocessing still owns the terminal business payload. Direct tools and validated
Knowledge Base answers remain complete deliveries. Public research comments are
separate temporary activity events, never provider reasoning or conversation history.
See the
frontend streaming migration guide.
Language detection, conversation metadata and attachment processing in the HTTP
handler still happen before the graph gate and need their own bounded inputs.
Nodes¶
START and END are LangGraph control markers, not executable business nodes.
The executable nodes in the main graph are:
| Node type | Node | Functional role |
|---|---|---|
| Control | check_long_term_memory |
Detect explicit, durable learner preferences and reconcile them with existing memory |
| Control | pre_supervisor_gate |
Approve safety before routing, memory or specialist tools |
| Routing node | supervisor |
Use a typed decision to select and transfer the turn to one enabled specialist |
| Specialist | career_chat_agent |
Career conversation, internal knowledge and contextual profile/job/skill/training information |
| Specialist | profile_analysis_agent |
Visual profile assessment, strengths and development priorities |
| Specialist | opportunities_suggestion_agent |
Opportunity search and objective-based recommendations |
| Specialist | training_finder_agent |
Training search, learning plans and simulation suggestions |
| Specialist | talent_finder_agent |
Internal talent search |
| Specialist | person_disambiguation_agent |
Resolve an ambiguous person reference |
| Specialist | task_helper_agent |
Help complete a professional task |
See the agent catalogue for responsibilities, outputs and tools.
Pre-supervisor decisions¶
The guardrail uses OpenAI Decisions with the configured mini profile and one
choice question named guardrail_decision: allow, doubt, or block.
Its input preserves the latest user text and the last ten nonempty human/assistant
text messages. Tool outputs are excluded. Ordinary missing business details
remain allow; doubt is reserved for unresolved safety or professional-scope
ambiguity. Confidence is diagnostic metadata, not a routing threshold.
Ordinary greetings, thanks and conversation openings are allowed without a
professional topic. Any substantive request accompanying a greeting still follows
the normal safety and scope rules.
allow produces no user-facing text and makes no response-generation call.
For doubt or block, a separate mini generation call receives the fixed
decision, recent context and requested language. It asks a short clarification or
phrases a refusal and supported next step, without tools or reclassification.
The response has a 512-token output budget and preserves configured reasoning
settings. Only an empty generated response uses the existing French/English
static boundary text. Generation failures stop the turn rather than allowing it.
All graphs, including direct specialist subgraphs, select native Decisions when
the resolved OpenAI mini profile supports it (gpt-6-luna). Other providers and
incompatible model mappings use a structured generative classification through
the same configured provider and mini profile. No tenant mappings are rewritten
and a failed native decision is never retried through generation.
A provider-level refusal to classify is an unavailable safety decision, not a
business block.
The gate applies outcomes in this order:
| Priority | Condition | Result |
|---|---|---|
| 1 | Guardrail returns block |
Return a refusal and end the turn |
| 2 | Guardrail returns doubt |
Ask for clarification and end the turn |
| 3 | Guardrail returns allow |
Start memory and the supervisor or selected specialist in parallel |
Career Chat and Knowledge Base¶
Career Chat is a group containing its conversational agent and a dedicated
Knowledge Base Deep Agent. transfer_to_knowledge_base records a handoff; a
conditional graph edge runs the specialist. It retrieves raw RAG passages and
configured public pages concurrently, compares evidence and synthesizes once.
The existing tenant MongoDB/PostgreSQL ranking, embedding configuration and
five-passage limit are preserved, without reindexing or an intermediate answer.
A supported answer ends the group verbatim. A miss or unavailable result returns to Career Chat with a matching tool-result message for one contextual follow-up. For an unavailable source, Career Chat explains that it could not check the internal information just now, rather than suggesting a completed search found no answer. It does not expose technical details or promise a recovery time. There is one handoff per turn; a new user turn starts with normal safety/routing. Only guardrails run before the supervisor. The supervisor never selects Knowledge Base independently, and transfers do not rerun guardrails or memory.
Both agents use the shared mini profile. Research state is isolated from
conversation checkpoints and its internal stream tokens are not sent to clients.
Tenant tool exclusions can disable transfer_to_knowledge_base. See the
Knowledge Base guide for URL configuration,
resource bounds, security, partial results and evaluations.
Supervisor and handoffs¶
The supervisor uses POST /v1/decisions with one choice question when the
configured OpenAI mini profile supports Decisions (gpt-6-luna). Azure,
Anthropic, OVH and incompatible OpenAI model mappings use the ordinary shared LLM
connector with a strict choice schema and the configured mini profile. Both
paths validate the selected destination and return only a graph transition.
Missing runtime configuration remains an error. Capability selection occurs
before the call; errors never trigger provider switching or a generative retry.
The choices contain only specialists enabled by the tenant. With exactly one enabled specialist, routing is deterministic and no Decisions request is needed; no model capability is required in that case. With none, the turn fails. Each choice describes the specialist's responsibility. Application-owned routing rules remain separate from JSON-encoded conversation evidence: message content, tool calls and results, the current agent, language and conversation summary. Tool records are evidence only and are never executed by Decisions. Long histories are summarized using the existing compaction policy before classification.
A graph Command selects the destination and records active_agent, together
with any summary update. No artificial assistant/tool-call pair is appended to
history. The supervisor cannot generate a user-facing answer. Out-of-scope
refusals remain the responsibility of the preceding guardrail.
After its internal model/tool loop completes,
the specialist routes to END. There are no general specialist-to-specialist
edges in the current graph: a normal turn selects one specialist.
One routing exception is intentional. A request initially directed to Training Finder can be redirected to Profile Analysis, or to Career Chat as a fallback, when it is detected as elaboration of a profile action plan and the corresponding agent is enabled.
Native Decisions uses the ordinary OpenAI SDK retry mechanism with at most three
attempts (max_retries=2). Transient HTTP and connection failures use exponential
backoff with jitter and provider retry hints, within one 15-second deadline that
includes retry waits. The structured provider path also has a 15-second deadline.
Provider refusals, invalid answers or distributions, an expired deadline and
exhausted retries abort the turn; cancellation propagates. Probabilities and confidence are observability
signals, not automatic routing thresholds. Ambiguous user requests retain the
existing Career Chat clarification instruction when that destination is enabled.
Decision traces use langgraph.supervisor; logs record bounded timing, token and
cost metadata without conversation payloads. Input/cache pricing is estimated
through the common cost calculator and output tokens are not charged. Pricing
estimates do not include regional surcharges.
See the official Decisions guide.
Specialist subgraphs¶
Seven subgraphs are compiled, following the same template:
START -> pre_supervisor_gate
├── block / doubt / unavailable -> stop
└── allow -> memory + selected specialist -> END
└── Career Chat may transfer to Knowledge Base
The available graph keys are the specialist node name followed by _subgraph.
For example, training_finder_agent_subgraph contains only Training Finder
after the shared gate.
All seven exist at runtime, but the current explicit entry points use:
| Caller | Selected graph |
|---|---|
| Standard Chatbot chat | main_graph |
| Chatbot Training Finder tool-function endpoint | training_finder_agent_subgraph |
| MCP opportunity tool | opportunities_suggestion_agent_subgraph |
| MCP talent tool | talent_finder_agent_subgraph |
Dedicated subgraphs preserve guardrails, checkpointing and long-term-memory evaluation while avoiding an unnecessary supervisor decision for an already-known functional destination.
Inside a specialist node¶
Each specialist created with LangChain create_agent is itself a bounded
model/tool loop:
specialist prompt + conversation state
|
v
model
┌───────┴────────┐
│ tool requested │ no tool / direct tool result
v v
tool(s) final result
│
└──────────────> model
Middleware can dynamically construct the prompt, shorten history, prevent duplicate recommendations, restrict parallel tool calls or remove tenant-excluded tools. The tool catalogue lists the tools attached to each node and their deterministic business logic.
Tenant-dependent availability¶
The compiled graph contains all specialists. At request time, the supervisor
filters its destination choices through chatbot.agents in the tenant
configuration:
- without an agent configuration, all specialists retain historical availability;
- with a configuration, only listed agents whose
activevalue is notfalsecan be selected; - supported agents can also remove named business tools with
tool_exclusion; - if business-tool filtering would leave a specialist with no tool, its original tool list is retained and a warning is logged.
Direct MCP wrappers additionally reject a disabled specialist before invoking its subgraph.
State, persistence and request context¶
The graph separates persisted conversational state from request-scoped context.
| State | Examples |
|---|---|
Conversation state (OverallState) |
Messages, conversation summary, guardrail decisions and turn-local knowledge source availability and handoff status, adaptive-card data, and IDs already returned by opportunity, training and talent searches |
Request context (ContextSchema) |
Tenant, data source, current user, databases, language, AI configuration, active agents, allowed tools and validated JWT |
| Checkpoints | Conversation graph state stored by the selected LangGraph checkpointer |
| Long-term store | Durable learner preferences stored separately from conversation checkpoints by the selected store |
The raw bearer token is provided to tools only after successful validation. In test or isolated execution, the checkpointer and store can use in-memory implementations.
Persistence selection is process-wide and shared by Chatbot and MCP:
- with
DATABASE_URL, AI Hub uses the official asynchronous PostgreSQL checkpointer and store inPG_SCHEMA; - without
DATABASE_URL, it usesMongoDBSaverandMongoDBStore; - a PostgreSQL initialization failure stops application startup and never falls back silently to MongoDB.
The MongoDB adapters are a deliberate synchronous-driver exception. The pinned
langgraph-checkpoint-mongodb 0.5.1 and langgraph-store-mongodb 0.4.0 require
PyMongo's synchronous client/collection; their async methods delegate to an
executor. See the tagged upstream checkpointer source
and store source.
AIHub isolates that client in common/connectors/mongodb/langgraph_mongodb.py.
LangGraph persistence owns one default-DSN client; HTTP applications and PwC
targets use only async clients. Connection checks, vendor constructors (including
their index inspection/backfill) and client shutdown run in the bounded blocking
pool. Startup cancellation drains construction and closes any resulting client;
shutdown drains an in-progress close before releasing the pool. Existing
checkpoint serialization, collection names and store data remain unchanged.
At application startup, AI Hub invokes the official
AsyncPostgresSaver.setup() and AsyncPostgresStore.setup() methods. A service
role with DDL privileges creates or migrates the vendor-owned PostgreSQL tables
and indexes. If and only if PostgreSQL rejects setup with
InsufficientPrivilege, AI Hub verifies that checkpoint_migrations and
store_migrations already match the installed packages and that every required
table exists, then continues without DDL. This supports personal runtime
accounts on an already initialized schema without hiding missing or outdated
objects. Their names (checkpoints, checkpoint_blobs,
checkpoint_writes and store, plus the two migration tables) intentionally
follow LangGraph's contract rather than the AIHub table prefix. Alembic does not
manage or drop this vendor schema. A dedicated pool supplies the required
autocommit and dictionary-row settings and is closed by the shared application
lifespan. Existing MongoDB checkpoints and long-term memories are not copied
when the backend changes.
These vendor tables are separate from the Chatbot application persistence
tables. LangGraph stores graph checkpoints and durable key/value memories in
checkpoints and store; AIHub stores user-visible conversations, chat-message
logs, feedback events, uploaded documents and tenant RAG records in
aihub_conversations, aihub_chat_message_log, aihub_events,
aihub_documents, aihub_rag_source_files, aihub_rag_chunks and
aihub_rag_metrics. When PostgreSQL resources exist, Chatbot routes these
application records through the AIHub-prefixed tables instead of the matching
MongoDB collections.
Search result identity¶
Successful talent, training and opportunity tools attach their returned IDs to
ToolMessage.artifact. The search middleware reads these structured IDs from the
last 25 messages to populate the existing exclusion state for explicit "show more"
requests. Display text is never parsed for identifiers; artifacts survive checkpoint
serialization and are not sent to the model. A new search keeps its existing
non-continuation behavior. Older messages without artifacts do not supply exclusions.
Specialist call limits¶
Career Chat, opportunity search, person disambiguation, profile analysis, talent
search, task guidance and training search use native ModelCallLimitMiddleware
and ToolCallLimitMiddleware. Each specialist invocation allows at most eight
model turns and six tool calls. An extra tool call receives an error tool result
without executing; the agent can still produce a grounded final response. If it
continues looping, the model-turn limit raises through the existing sanitized graph
error boundary. Knowledge Base keeps its existing independent four-turn budget.
These are per-invocation bounds, not conversation quotas. Native run counters are not checkpointed: a later user turn or HITL resume starts a new allowance. Completed HITL work retains its existing durable task behavior. A model turn can contain both private action selection and public answer generation; nested tool LLM calls and provider transport retries are not counted as separate agent turns. These limits therefore bound orchestration loops, not total tokens or invoice cost. Semantic model profiles, reasoning configuration and transport retry policies are unchanged.
Recoverable tool failures¶
Task guidance, profile analysis and objective retrieval use native LangChain
ToolErrorMiddleware at the agent boundary. The middleware returns each tool's
existing sanitized error text with its original name and call ID and status=error.
Missing profile data keeps its distinct domain message. Other tools are not covered
by this policy: partial-result processing and API-specific boundaries retain their
own error handling. Interrupts, parent commands and cancellation propagate unchanged;
this middleware adds no retries. Logs retain the tool-call correlation and causal
error, while messages and checkpoints receive only the safe public text.
Conversation size management¶
All conversational specialists share LangChain ContextEditingMiddleware and
trim_messages. Above 8,000 approximate tokens, native ClearToolUsesEdit clears
older tool results while retaining the three most recent results. Completed turns
are then trimmed to a target of 30 messages and 12,000 approximate tokens, starting
on a human message. The current user turn and its tool-call/result pairs remain
complete, even when that turn alone exceeds the targets. Summaries are reattached
as untrusted context after trimming.
Business-specific exclusions remove transfer and search-tool calls together with
their results using native filter_messages. All editing affects only the model
request copy: persisted history and search identity artifacts remain intact.
The supervisor also summarizes long conversations:
| Setting | Current value |
|---|---|
| Summary trigger | 24,000 tokens |
| Recent context retained | 8,000 tokens |
| Maximum source passed to summarization | 16,000 tokens |
| Summary target | 700 words |
Summaries preserve supported facts and explicitly established action or selection statuses. Unmentioned actions, resources and completion states are omitted rather than described as unfinished, unselected or undocumented. Empty headings and missing-information placeholders are omitted too; relevant explicit statements of non-completion or absence remain part of the summary. Language preferences are retained only when explicitly expressed by the user in the summarized history. Message language and configured output locale alone do not establish a preference.
The summary is persisted in state and supplied as labeled HumanMessage data,
separately from system instructions. Stored preferences follow the same rule in
the dynamic prompts that use them. A shared static policy treats summaries,
preferences, documents and tool results as untrusted context. See the
input-security guide for scope and limitations.
Long-term memory decisions¶
After safety approval, memory evaluation still runs alongside routing or the
specialist. With compatible OpenAI mini, memory_update_decision selects
skip or update. Skipped turns make no memory-generation call. An update invokes
memory_update_generation on mini to reconcile the complete preferences list,
preserving first-person wording, each fact's language and dates for unchanged
items. Explicit corrections, supersessions and retractions remain supported;
retracting the last fact can persist an empty list. No confirmation is requested.
Other providers retain the combined structured MemoryUpdateDecision call.
The existing best-effort memory boundary owns failures; cancellation propagates.
Prompt, model and observability profiles¶
Agents use tenant-configured models through dynamic mini and large profiles.
Supervisor and guardrail classification use the mini profile through Decisions
when supported, otherwise through ordinary structured generation. Native
Decisions does not accept generation-only temperature or reasoning settings;
legacy generative paths retain configured reasoning settings. Conversation
compaction uses the mini profile with its own output limit. Each
call carries a stable task label such as
langgraph.supervisor, langgraph.guardrails_decision,
langgraph.guardrails_response or langgraph.training_finder_agent, which makes model
usage and costs attributable in LangSmith.
Dynamic prompts are structured as reusable instructions followed by request-specific context. Provider prompt caching is enabled only when it is requested, supported by the provider and the stable prefix is long enough. See LLM runtime.
Execution budgets and error ownership¶
Graph nodes share their policies in src/chatbot/agentic/execution_policy.py.
The supervisor and optional long-term memory each have a 30-second wall-clock
budget. Each complete specialist invocation has a 180-second budget, including
its nested tools and handoffs. The same policies apply to the main graph and
independently invoked specialist graphs (including MCP).
The guardrail retains its 15-second combined decision/response budget. Knowledge research retains its 45-second budget, also for direct research calls. Existing RAG/web-source and profile-analysis operation budgets remain in effect. These limits cover the named operation, not HTTP preprocessing or persistence. Adjust the centralized constants/policies to tune the limits; idle timeouts are not enabled because the provider adapter may wait silently for a complete response.
LangGraph error handlers end failed memory work without cancelling the parallel response. Knowledge research errors close the pending handoff with one sanitized unavailability result and return to Career Chat once. An unavailable source is not evidence that the requested information does not exist. Guardrail failure stops routing and memory; supervisor or specialist failure aborts the turn with the existing safe error response. No graph retry or alternate provider is added. Cancellation propagates and drains owned async work; already completed external writes are not rolled back. Node logs retain correlation and report the error category plus elapsed time and budget for node timeouts.
The existing knowledge success/miss/unavailable/no-source evaluation rubrics are unchanged. Execution deadlines and cancellation are covered by deterministic graph integration tests, without duplicating semantic cases or paid calls.
Generic user interruptions¶
HTTP chat sets the request-scoped supports_interactions capability. The shared
request_clarification_tool suspends essential clarification with interrupt.
Learning-plan, person/objective selection and empty-search tools own their
workflow-specific pauses. All use the same interaction output and text resume
contract, returning control to the HTTP handler. LangGraph task steps cache serializable preparation and
scope decisions, so restarting the tool node does not reload profile/history or
repeat completed decisions. No database client or live asynchronous task is
stored in checkpoints. Each new question batch has its own public interaction ID and ordered question IDs,
separate from LangGraph's internal interrupt ID.
The HTTP boundary checks conversation ownership, tenant/user/conversation values
in the checkpoint, the current public ID and an atomic resume reservation. It
validates exact question coverage and reuses the safety gate on every answer text because Command(resume=...) does not
start again at the graph's initial gate. Failed attempts release their own
reservation; a bounded expiring lease protects concurrent workers. MongoDB uses
find_one_and_update; PostgreSQL uses a single tenant-scoped conditional JSONB
UPDATE on the existing conversation document. The unique conversation indexes
cover these lookups; no schema migration or new index is needed.
A paused turn is never pruned or treated as an ordinary completed tool result.
On completion, clarification exchanges follow the resolved tool pair in graph
history, preserving provider-neutral message ordering. Tool wrappers must
propagate GraphInterrupt before handling ordinary exceptions. Cancellation
retains its native semantics and request connections are never kept open while
waiting for a person. See the frontend contract.