docs: describe DCP context engine design
This commit is contained in:
parent
28b9df0d07
commit
bac2955df9
|
|
@ -70,10 +70,13 @@ run_conversation()
|
||||||
- chat_completions: OpenAI format as-is
|
- chat_completions: OpenAI format as-is
|
||||||
- codex_responses: convert to Responses API input items
|
- codex_responses: convert to Responses API input items
|
||||||
- anthropic_messages: convert via anthropic_adapter.py
|
- anthropic_messages: convert via anthropic_adapter.py
|
||||||
6. Inject ephemeral prompt layers (budget warnings, context pressure)
|
6. Let the active context engine transform the API-call copy, if supported
|
||||||
7. Apply prompt caching markers if on Anthropic
|
- DCP-style engines can add refs, compression placeholders, and nudges
|
||||||
8. Make interruptible API call (_interruptible_api_call)
|
- canonical conversation history must remain unchanged
|
||||||
9. Parse response:
|
7. Inject ephemeral prompt layers (budget warnings, context pressure)
|
||||||
|
8. Apply prompt caching markers if on Anthropic
|
||||||
|
9. Make interruptible API call (_interruptible_api_call)
|
||||||
|
10. Parse response:
|
||||||
- If tool_calls: execute them, append results, loop back to step 5
|
- If tool_calls: execute them, append results, loop back to step 5
|
||||||
- If text response: persist session, flush memory if needed, return
|
- If text response: persist session, flush memory if needed, return
|
||||||
```
|
```
|
||||||
|
|
@ -160,6 +163,19 @@ Some tools are intercepted by `run_agent.py` *before* reaching `handle_function_
|
||||||
|
|
||||||
These tools modify agent state directly and return synthetic tool results without going through the registry.
|
These tools modify agent state directly and return synthetic tool results without going through the registry.
|
||||||
|
|
||||||
|
|
||||||
|
### Context-engine tools
|
||||||
|
|
||||||
|
The active context engine can expose tools via `get_tool_schemas()`. These tools
|
||||||
|
are injected into the model-visible tool list and routed back to
|
||||||
|
`handle_tool_call()` before normal registry dispatch.
|
||||||
|
|
||||||
|
This is how DCP-style context management exposes a model-callable `compress`
|
||||||
|
tool. The tool updates context-engine state, then the next API-call transform
|
||||||
|
applies compression blocks to the outbound message copy. It should not mutate
|
||||||
|
the canonical transcript unless the engine explicitly documents a
|
||||||
|
transcript-mutating mode.
|
||||||
|
|
||||||
## Callback Surfaces
|
## Callback Surfaces
|
||||||
|
|
||||||
`AIAgent` supports platform-specific callbacks that enable real-time progress in the CLI, gateway, and ACP integrations:
|
`AIAgent` supports platform-specific callbacks that enable real-time progress in the CLI, gateway, and ACP integrations:
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ This page is the top-level map of Hermes Agent internals. Use it to orient yours
|
||||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||||
│ │ │ │ │
|
│ │ │ │ │
|
||||||
│ ┌──────┴───────┐ ┌──────┴───────┐ ┌──────┴───────┐ │
|
│ ┌──────┴───────┐ ┌──────┴───────┐ ┌──────┴───────┐ │
|
||||||
│ │ Compression │ │ 3 API Modes │ │ Tool Registry│ │
|
│ │ Context Mgmt │ │ 3 API Modes │ │ Tool Registry│ │
|
||||||
│ │ & Caching │ │ chat_compl. │ │ (registry.py)│ │
|
│ │ & Caching │ │ chat_compl. │ │ (registry.py)│ │
|
||||||
│ │ │ │ codex_resp. │ │ 61 tools │ │
|
│ │ │ │ codex_resp. │ │ 61 tools │ │
|
||||||
│ │ │ │ anthropic │ │ 52 toolsets │ │
|
│ │ │ │ anthropic │ │ 52 toolsets │ │
|
||||||
|
|
@ -64,6 +64,7 @@ hermes-agent/
|
||||||
│ ├── prompt_builder.py # System prompt assembly
|
│ ├── prompt_builder.py # System prompt assembly
|
||||||
│ ├── context_engine.py # ContextEngine ABC (pluggable)
|
│ ├── context_engine.py # ContextEngine ABC (pluggable)
|
||||||
│ ├── context_compressor.py # Default engine — lossy summarization
|
│ ├── context_compressor.py # Default engine — lossy summarization
|
||||||
|
│ ├── dcp_context_engine.py # Optional DCP-style model-guided context engine
|
||||||
│ ├── prompt_caching.py # Anthropic prompt caching
|
│ ├── prompt_caching.py # Anthropic prompt caching
|
||||||
│ ├── auxiliary_client.py # Auxiliary LLM for side tasks (vision, summarization)
|
│ ├── auxiliary_client.py # Auxiliary LLM for side tasks (vision, summarization)
|
||||||
│ ├── model_metadata.py # Model context lengths, token estimation
|
│ ├── model_metadata.py # Model context lengths, token estimation
|
||||||
|
|
@ -134,6 +135,15 @@ hermes-agent/
|
||||||
└── tests/ # Pytest suite (~3,000+ tests)
|
└── tests/ # Pytest suite (~3,000+ tests)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Context management
|
||||||
|
|
||||||
|
Context management is handled through the `ContextEngine` interface. The default
|
||||||
|
engine is `ContextCompressor`, which performs host-triggered summarization. Other
|
||||||
|
engines can expose tools and transform the provider-bound API message copy. A
|
||||||
|
DCP-style engine uses that path to keep the stored transcript complete while
|
||||||
|
sending compressed blocks, refs, and nudges to the model.
|
||||||
|
|
||||||
## Data Flow
|
## Data Flow
|
||||||
|
|
||||||
### CLI Session
|
### CLI Session
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,88 @@ Configure via `hermes plugins` → Provider Plugins → Context Engine, or edit
|
||||||
|
|
||||||
For building a context engine plugin, see [Context Engine Plugins](/docs/developer-guide/context-engine-plugin).
|
For building a context engine plugin, see [Context Engine Plugins](/docs/developer-guide/context-engine-plugin).
|
||||||
|
|
||||||
|
|
||||||
|
## DCP Context Engine
|
||||||
|
|
||||||
|
Hermes can also run a DCP-style context engine with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
context:
|
||||||
|
engine: "dcp"
|
||||||
|
```
|
||||||
|
|
||||||
|
DCP mode is different from the built-in `ContextCompressor`. The built-in
|
||||||
|
compressor is host-driven: Hermes decides that the session is too large, calls
|
||||||
|
an auxiliary summarization model, and replaces the stored message list with a
|
||||||
|
compressed transcript. DCP mode is model-guided: Hermes exposes a `compress`
|
||||||
|
tool, adds stable message and block references to the outbound request, and
|
||||||
|
lets the active model compress completed ranges when it has enough semantic
|
||||||
|
context to know what is safe to replace.
|
||||||
|
|
||||||
|
The DCP invariant is:
|
||||||
|
|
||||||
|
> The canonical session transcript remains complete. DCP transforms only the
|
||||||
|
> API-call copy of the messages sent to the provider.
|
||||||
|
|
||||||
|
A DCP engine owns separate compression state:
|
||||||
|
|
||||||
|
- message refs such as `m0001`, `m0002`
|
||||||
|
- compression block refs such as `b1`, `b2`
|
||||||
|
- active block summaries
|
||||||
|
- duplicate-tool and old-error pruning state
|
||||||
|
- compression stats and nudge cadence
|
||||||
|
|
||||||
|
### DCP `compress` tool
|
||||||
|
|
||||||
|
When DCP mode is active, the context engine may expose a `compress` tool. The
|
||||||
|
tool does not rewrite the stored transcript. Instead, it records compression
|
||||||
|
blocks. The next API-call transform applies those blocks to the outbound copy.
|
||||||
|
|
||||||
|
DCP supports two modes:
|
||||||
|
|
||||||
|
- `range`: compress one or more contiguous spans using `{startId, endId, summary}`.
|
||||||
|
- `message`: compress individual high-volume messages using `{messageId, topic, summary}`.
|
||||||
|
|
||||||
|
Range mode is the default because it preserves chronology and usually gives
|
||||||
|
the model enough context to summarize closed work accurately. Message mode is
|
||||||
|
more surgical and should be treated as experimental until provider-format and
|
||||||
|
cache behavior are well tested.
|
||||||
|
|
||||||
|
### DCP nudges and automatic strategies
|
||||||
|
|
||||||
|
DCP mode can inject ephemeral nudges when context pressure rises. Nudges tell
|
||||||
|
the model to call `compress` before continuing if a completed topic is safe to
|
||||||
|
compact. The engine may also run cheap automatic strategies over the outbound
|
||||||
|
copy:
|
||||||
|
|
||||||
|
- deduplicate repeated tool calls with the same tool name and arguments, keeping
|
||||||
|
the latest output
|
||||||
|
- purge bulky old failed-tool inputs while preserving the error text
|
||||||
|
- protect recent user turns and configured protected tools
|
||||||
|
|
||||||
|
These strategies are DCP-state transformations, not transcript edits.
|
||||||
|
|
||||||
|
### Interaction with existing compression
|
||||||
|
|
||||||
|
When `context.engine: "dcp"`, the built-in `compression:` settings do not drive
|
||||||
|
normal compaction. DCP should return `False` from `should_compress()` during
|
||||||
|
normal operation and rely on nudges plus the `compress` tool. The built-in
|
||||||
|
`ContextCompressor` may still be used as an emergency fallback for hard context
|
||||||
|
limit failures, but it should not run as a parallel primary compressor.
|
||||||
|
|
||||||
|
Gateway session hygiene remains a safety net. Because hygiene compression
|
||||||
|
operates before the agent starts and may mutate gateway history, DCP-aware
|
||||||
|
hygiene behavior must be handled deliberately rather than implicitly reusing
|
||||||
|
the built-in compressor path.
|
||||||
|
|
||||||
|
### Prompt caching
|
||||||
|
|
||||||
|
DCP must run before provider cache-control markers are applied so prompt caching
|
||||||
|
sees the actual outgoing request. The transform should be deterministic and
|
||||||
|
should avoid modifying old stable content on every turn. Compression blocks and
|
||||||
|
automatic pruning should change the cached prefix only when compression state
|
||||||
|
changes, not as a side effect of moving counters or timestamps.
|
||||||
|
|
||||||
## Dual Compression System
|
## Dual Compression System
|
||||||
|
|
||||||
Hermes has two separate compression layers that operate independently:
|
Hermes has two separate compression layers that operate independently:
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,46 @@ These have sensible defaults in the ABC. Override as needed:
|
||||||
| `handle_tool_call(name, args, **kwargs)` | Returns error JSON | You implement tool handlers |
|
| `handle_tool_call(name, args, **kwargs)` | Returns error JSON | You implement tool handlers |
|
||||||
| `should_compress_preflight(messages)` | Returns `False` | You can do a cheap pre-API-call estimate |
|
| `should_compress_preflight(messages)` | Returns `False` | You can do a cheap pre-API-call estimate |
|
||||||
| `get_status()` | Standard token/threshold dict | You have custom metrics to expose |
|
| `get_status()` | Standard token/threshold dict | You have custom metrics to expose |
|
||||||
|
| `transform_api_messages(api_messages, **kwargs)` | Returns `api_messages` unchanged | You need to alter the provider-bound copy without mutating stored history |
|
||||||
|
|
||||||
|
|
||||||
|
## API-call-time transforms
|
||||||
|
|
||||||
|
Context engines that need DCP-style behavior can override
|
||||||
|
`transform_api_messages()` to transform the outbound API-call copy of the
|
||||||
|
conversation:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def transform_api_messages(
|
||||||
|
self,
|
||||||
|
api_messages: list[dict],
|
||||||
|
*,
|
||||||
|
canonical_messages: list[dict],
|
||||||
|
system_prompt: str,
|
||||||
|
tools: list[dict] | None,
|
||||||
|
api_call_count: int,
|
||||||
|
model: str,
|
||||||
|
provider: str | None,
|
||||||
|
session_id: str | None,
|
||||||
|
) -> list[dict]:
|
||||||
|
return api_messages
|
||||||
|
```
|
||||||
|
|
||||||
|
This hook is for ephemeral, model-facing context changes such as message refs,
|
||||||
|
compression block placeholders, and context-pressure nudges. It must not mutate
|
||||||
|
`canonical_messages`, because those messages are the authoritative session
|
||||||
|
transcript.
|
||||||
|
|
||||||
|
Transform implementations must preserve provider-valid message order:
|
||||||
|
|
||||||
|
- keep assistant `tool_calls` messages adjacent to their tool results
|
||||||
|
- do not orphan tool results
|
||||||
|
- do not create consecutive roles that the provider rejects
|
||||||
|
- return OpenAI-format messages that still pass Hermes sanitization
|
||||||
|
|
||||||
|
The hook should run before provider-specific cache-control placement. Engines
|
||||||
|
should avoid churn in the stable prompt prefix because unnecessary changes lower
|
||||||
|
prompt-cache hit rates.
|
||||||
|
|
||||||
## Engine tools
|
## Engine tools
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,29 @@ You are a CLI AI Agent. Try not to use markdown but simple text
|
||||||
renderable inside a terminal.
|
renderable inside a terminal.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Context-engine API-call-time transforms
|
||||||
|
|
||||||
|
After Hermes assembles the canonical conversation into provider-bound
|
||||||
|
`api_messages`, the active context engine may transform that API-call copy. This
|
||||||
|
keeps persisted session history separate from ephemeral model-facing context.
|
||||||
|
|
||||||
|
DCP-style engines use this layer to add:
|
||||||
|
|
||||||
|
- stable message refs such as `m0001`
|
||||||
|
- compression block refs such as `b1`
|
||||||
|
- compact placeholders for compressed ranges
|
||||||
|
- context-pressure nudges that teach the model when to call a context tool
|
||||||
|
- cheap outbound-only pruning such as duplicate tool output placeholders
|
||||||
|
|
||||||
|
The transform must not rewrite the canonical transcript. If a session is saved,
|
||||||
|
it should still contain the full original conversation unless a user explicitly
|
||||||
|
invokes a separate transcript-mutating command.
|
||||||
|
|
||||||
|
Because this layer changes what the provider sees, it should run before prompt
|
||||||
|
cache-control markers are applied. Engines should keep the transformed stable
|
||||||
|
prefix deterministic and avoid moving counters or timestamps in old context.
|
||||||
|
|
||||||
## How SOUL.md appears in the prompt
|
## How SOUL.md appears in the prompt
|
||||||
|
|
||||||
`SOUL.md` lives at `~/.hermes/SOUL.md` and serves as the agent's identity — the very first section of the system prompt. The loading logic in `prompt_builder.py` works as follows:
|
`SOUL.md` lives at `~/.hermes/SOUL.md` and serves as the agent's identity — the very first section of the system prompt. The loading logic in `prompt_builder.py` works as follows:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue