Ctrl K
ComunidadGuías…

docs/TURN_LIFECYCLE.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# The turn lifecycle

One request to the full runtime becomes one GMI turn. Five stages own it, in this order; every line names the code that runs it. When the model requests a tool the host executes, `processRequest()` yields that chunk and returns; the host continues the same turn through `handleToolResult()`, `handleToolResults()` or, with the pending request stored on the conversation (`getPendingExternalToolRequest()`), `resumeExternalToolRequest()`. The streams those calls return pass through the same output guardrails as the turn's stream. A turn stops for the host only when the GMI's output is not final (`isFinal: false`) and names calls, and a continuation stops again only by the same rule ([`externalToolPause.ts`](https://github.com/framerslab/agentos/blob/master/src/api/runtime/externalToolPause.ts)). The calls a final output lists ran in the GMI's own tool rounds, or were refused there by the tool-call gate: the runtime ends the turn or the continuation with `FINAL_RESPONSE`, which lists them in `finalToolCalls`, and saves no pending request for them. Every output that does not stop for the host ends the turn or the continuation, marked final or not, with one `FINAL_RESPONSE` that both build the same way ([`turnFinalResponse.ts`](https://github.com/framerslab/agentos/blob/master/src/api/runtime/turnFinalResponse.ts)): the reply with its plain text in `finalResponseTextPlain`, the calls, the usage, the sources and, on a structured turn, `structured`. The conversation keeps the reply and never the calls of the GMI's own tool rounds, whose results only the GMI's history holds, so the next turn sends the model no call without its result. With the rolling summary on (`rollingSummaryCompactionConfig`), a host's tool round that the conversation records goes into the summary whole or reaches the next turn's history whole, never split between them ([`RollingSummaryCompactor.ts`](https://github.com/framerslab/agentos/blob/master/src/core/conversation/RollingSummaryCompactor.ts), [`conversation-history.ts`](https://github.com/framerslab/agentos/blob/master/src/api/runtime/turn-phases/conversation-history.ts)). The compaction profiles a turn's mode picks share the conversation's one summary state, which records the head it was built with, so a turn under a profile with another `headMessagesToKeep` sends every message the summary left out and none it took; a conversation an earlier release compacted keeps any gap it has ([Conversation history](./GMI.md#conversation-history)). A pause is continued once ([`ExternalToolResultHandler.ts`](https://github.com/framerslab/agentos/blob/master/src/api/runtime/ExternalToolResultHandler.ts)). Results passed on its stream continue it only while the conversation holds the pause that stream saved and each result answers one of its calls; otherwise the GMI takes none of them and `handleToolResult()` and `handleToolResults()` yield one error chunk: `SYS_INVALID_STATE` while the stream is open (its pause is being continued, or the stream's next pause replaced it), and `TOOL_ERROR` once the stream is closed (its turn or continuation ended, a resume took the pause, a newer turn ended it, or an output guardrail blocked it). A resume ends the stream the pause was saved on. A call of the pause that the results leave out is answered with a tool result that says it has none (code `EXTERNAL_TOOL_NOT_RUN`), after the results the host passed and before the GMI writes again: the conversation records it, and a GMI that takes results together (`IGMI.handleToolResults()`, as `GMI` does) reads it beside the host's results, so its own history answers every call of the pause too; an `IGMI` that takes one result at a time reads the host's result alone. Pass every result of a pause in one `handleToolResults()` call. A new turn on a conversation that holds a pause ends the pause once the runtime accepts the turn and before the turn records its input: each call of the pause is answered the same way, right after the pause's assistant message, the pending request is cleared, and the pause's stream is closed. A turn the runtime refuses before then (an organization a strict single-tenant runtime refuses, or a turn plan that fails) leaves the pause as it was. With `enableConversationalPersistence` off the runtime records no message of a pause in the conversation, neither its call nor a refusal, and a resume continues on the GMI's own history: the conversation's history replaces the GMI's only when it holds messages, as a turn's prompt reads it. A session's GMI serves every conversation of the session ([`GMIManager.getOrCreateGMIForSession()`](https://github.com/framerslab/agentos/blob/master/src/cognition/substrate/GMIManager.ts) keys it by session) and records every turn in its own history; a turn builds its prompt from its conversation's stored history, and a continuation from the GMI's. So a continuation runs on a GMI that holds the paused turn: the GMI the turn paused on keeps its own history while that history holds no turn of another conversation, since it also holds the calls the GMI ran itself in the turn; otherwise the runtime sets the GMI's history, and its session, conversation and organization from the pause's stream, before the conversation records the results; a GMI whose last turn was another conversation's then also drops what it kept for that turn (its memory scope, tool-call gate, learning preparation and model hop), so the continuation starts as on a GMI built for it and asks the learning layer for a preparation of its own. The continuation's calls are judged with the tool-call gate it is given, or else with the one its turn ran with, which the runtime keeps with the turn's stream (`AgentOSOrchestrator.orchestrateToolResult()` takes none). The history it sets is the GMI's own history of the paused turn when the runtime kept one, which a turn that stops for the host leaves with the turn's stream: the GMI's whole history (`IGMI.getConversationHistory()`) when it holds that conversation alone, and, when a turn of another conversation ran on the GMI before the paused turn, the conversation's stored history before the turn followed by the turn's own messages as the GMI holds them (`IGMI.getLastTurnHistory()`); either one ends with an assistant message naming each call of the pause the GMI did not record, as when a host's own tool layer stops the turn after the GMI ran it. Otherwise (an `IGMI` that gives neither) it is the conversation's stored history, which holds the conversation's messages and the pause's calls but not the calls the GMI ran itself in the turn. When the GMI the turn paused on was shut down, as a turn on the session that asks for another persona shuts it down, the continuation runs on the session's GMI for the paused turn's persona, acquired under the paused turn's organization as the turn acquired its own (which in turn shuts down a session GMI of another persona). The manager hands out no GMI that is shutting down: an acquisition for a session whose GMI it deactivates (a persona refresh, `cleanupInactiveGMIs()`) waits until that GMI has shut down and then gets a new one. A conversation that holds no history of the paused turn (persistence off) cannot continue on another GMI, so the results are refused with an error and the pause stays. With persistence off the GMI's own history is the only record of the session's conversations, so a continuation on a GMI that ran a turn of another conversation reads that turn too, as each turn of the session reads the others: setting another history would erase it, and the runtime logs a warning instead. The continuation's tool calls and reply are judged with the sources the paused turn retrieved, which the runtime keeps with the turn's stream and sets on such a GMI; a resume takes them, and the GMI's own history of the turn, from that stream while this process holds it, and after a restart continues with no sources, from the conversation's stored history. A resume takes results only for the calls of the pause it names, as the pause's stream does, and is refused otherwise (`SYS_INVALID_STATE`). A continuation that fails before the conversation records its results (the store refuses to save that the pause was taken, or a result has no JSON form), and a resume whose organization the runtime refuses, leave the pause as it was: `getPendingExternalToolRequest()` returns it, a resume continues it, and a new turn ends it. A continuation that fails after the conversation recorded its results keeps them. A pause that a turn or a continuation saved before it failed (the store refuses that save) ends the way a new turn ends one, since the host never read its request. AgentOS does not queue a conversation's operations: start the conversation's next turn once the stream of a continuation or a resume has ended, since a turn that starts while one runs finds no pause to end, and its input lands among the continuation's messages. A continuation streams the sources of the GMI's output as a `METADATA_UPDATE` before its text, as a turn does. The host stops reading at the first request it acts on and runs only the calls it names, so a turn that stops for the host streams one actionable request, after it saves the pause, naming the calls it saved. A request for calls the host runs that the GMI streams waits for the GMI's output: the host reads it only when it is the one such request the GMI streamed and names exactly the calls the turn stops for, and otherwise reads a request the runtime builds for them; a turn that does not stop for the host streams none of them, so the host reads on to the reply. A request for calls the GMI runs itself (`executionMode: 'internal'`) streams as it comes, and the stream never carries a request that names no call, which would give the host nothing to run.

| Stage | Owner | What happens |
|---|---|---|
| 1. Facade | [`AgentOS.processRequest()`](https://github.com/framerslab/agentos/blob/master/src/api/AgentOS.ts) | Fills `selectedPersonaId` from `defaultPersonaId` when the request has none, applies the self-improvement session overrides and skill prompt context, negotiates the language, and evaluates the input guardrails through [`evaluateInputGuardrails`](https://github.com/framerslab/agentos/blob/master/src/safety/guardrails/guardrailDispatcher.ts); a blocked input ends the request before any turn starts. When a guard named in `requiredGuardrails` is not active, it yields one `SYS_GUARDRAIL_REQUIRED_MISSING` error chunk and runs nothing. It does not validate the input (phase 1 of the pipeline does), and it performs no authentication or rate limiting; the host does both before calling it. |
| 2. Orchestrator | [`AgentOSOrchestrator`](https://github.com/framerslab/agentos/blob/master/src/api/runtime/AgentOSOrchestrator.ts) | Registers the stream and runs the preparation pipeline, then the GMI turn, and converts the result. |
| 3. Preparation | [`TurnExecutionPipeline.prepareTurn()`](https://github.com/framerslab/agentos/blob/master/src/api/runtime/TurnExecutionPipeline.ts) | Twelve phases: input validation, GMI acquisition ([`GMIManager.getOrCreateGMIForSession()`](https://github.com/framerslab/agentos/blob/master/src/cognition/substrate/GMIManager.ts)), stream context, GMI input construction, turn planning, adaptive execution policies, organization context and long-term memory policy, inbound message persistence (after the end of a pause the conversation holds), rolling summary compaction, prompt profile routing, long-term memory retrieval, and history assembly with metadata persistence. |
| 4. The GMI turn | [`GMI.processTurnStream()`](https://github.com/framerslab/agentos/blob/master/src/cognition/substrate/GMI.ts) | Sentiment scoring when the persona enables it; the RAG trigger; memory context from [`CognitiveMemoryBridge`](https://github.com/framerslab/agentos/blob/master/src/cognition/substrate/CognitiveMemoryBridge.ts) when cognitive memory is attached; the prompt from the [`PromptEngine`](https://github.com/framerslab/agentos/blob/master/src/core/llm/PromptEngine.ts); the streaming model call; the tool loop through [`ToolOrchestrator`](https://github.com/framerslab/agentos/blob/master/src/core/tools/ToolOrchestrator.ts), five iterations by default; history and memory updates; the metaprompts. |
| 5. Delivery | [`GMIChunkTransformer`](https://github.com/framerslab/agentos/blob/master/src/api/runtime/GMIChunkTransformer.ts), [`wrapOutputGuardrails`](https://github.com/framerslab/agentos/blob/master/src/safety/guardrails/guardrailDispatcher.ts), [`StreamingManager`](https://github.com/framerslab/agentos/blob/master/src/core/streaming/StreamingManager.ts) | The orchestrator pushes the converted chunks into the streaming manager; the facade reads them back through an `AsyncStreamClientBridge` registered as one client, evaluates the output guardrails on that stream (in hold mode it holds the `TEXT_DELTA` chunks until the guardrails have judged the whole reply), and yields the result. Other clients of the stream receive the chunks unguarded. |

## What a GMI emits

`RAG_SOURCES_AVAILABLE` when retrieval found sources, `TEXT_DELTA` for streamed text, `TOOL_CALL_REQUEST` when the model asks for a tool, `USAGE_UPDATE` with token counts, `STEP_FINISHED` when a model step completes, `TOOL_RESULT` for each result of the GMI's tool round, `ERROR` when the turn fails, and a `FINAL_RESPONSE_MARKER` last ([`IGMI.ts`](https://github.com/framerslab/agentos/blob/master/src/cognition/substrate/IGMI.ts); payloads on [Generalized Mind Instances](./GMI.md#output-stream)). The chunk-type enum also defines `REASONING_STATE_UPDATE`, `SYSTEM_MESSAGE`, `LATENCY_REPORT` and `UI_COMMAND`, which `GMI` does not emit.

Within a model step, each provider chunk yields its `TEXT_DELTA` and `TOOL_CALL_REQUEST` chunks, then its `USAGE_UPDATE`. The step's `STEP_FINISHED` follows its last chunk, and the `TOOL_RESULT` chunks of its tool round follow the `STEP_FINISHED`; the next step's chunks come after them. A step that fails emits no `STEP_FINISHED`: the turn's `ERROR` follows the step's last chunk. `USAGE_UPDATE` arrives for every provider chunk that carries usage and holds the request's running total, so a step's last `USAGE_UPDATE` is that step's usage. On a GMI with a completion gateway, an attempt that fails before any output emits none of its chunks; when the provider billed it, its usage arrives as a `USAGE_UPDATE` with `metadata.attemptFailed: true`, before the next hop's chunks or the turn's `ERROR`. A step that fails after output reports its bill the same way, with or without a gateway: one `USAGE_UPDATE` with `metadata.attemptFailed: true`, before the turn's `ERROR`.

The transformer maps the chunks to `AgentOSResponseChunkType` values. It does not forward `USAGE_UPDATE`, `STEP_FINISHED` or `TOOL_RESULT`; the turn's usage reaches the stream on `FINAL_RESPONSE.usage`. An error inside the GMI turn reaches the stream as an `ERROR` chunk whose message carries the original failure and whose code is `GMI_PROCESSING_ERROR` unless the error carries its own (an error chunk in the provider's stream carries `LLM_PROVIDER_ERROR`). On a `processRequest()` stream the turn's `FINAL_RESPONSE` still follows, with that code and message in `error` and the usage counted before the failure in `usage`. An error outside the GMI turn ends the stream with an `ERROR` chunk, and an input blocked by a guardrail ends the request before the turn starts.

## What persists between turns

The GMI's conversation history (a window of 20 messages unless the persona sets `conversationContextConfig.maxMessages`), its reasoning trace (500 entries by default, configurable per persona), its mood and user context in working memory, and, when attached, the cognitive memory traces it encoded. Across a restart, a storage adapter keeps agent-tier forged tools and the inbound messages the pipeline persists, a durable memory brain keeps the cognitive traces, and everything else is rebuilt.

The conversation the runtime records keeps `maxHistoryLengthMessages` messages (100 by default; a limit of 0 keeps every message): past that it drops the messages after its first `messagesToKeepVerbatimHead`, oldest first, or summarizes them when `enableAutomaticSummarization` is on and the conversation has a utility AI to summarize with (the full runtime gives every conversation one), and a drop keeps the summary right after those first messages and the last summary after them, or the last one alone when the limit leaves room for only one. With the rolling summary off the next turn's history is the messages it keeps, without its errors and thoughts. Neither cut splits a tool round: a round the kept messages reach into stays whole, past that number, and a summary leaves out a round whose calls still wait for their results ([`ConversationContext.ts`](https://github.com/framerslab/agentos/blob/master/src/core/conversation/ConversationContext.ts)).