Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,35 @@ All notable changes to this project are documented here. The format is based on

### Added

- `Capability\ScopedCapabilityInterface` and `ScopeArea`: a capability names an area of what the
assistant does, and the prompt's "Stay within …" line lists the registered ones by priority
("a and b", "a, b, and c"). Without any, it says `StaticPromptBuilder::DEFAULT_SCOPE`.

- Runtime settings: `Config\ConfigOverridesInterface` (no changes by default) lays a host's saved
settings over the configuration for the current request, through `RuntimeAgentConfig`, the
`Config\AgentConfigProviderInterface` that `AgentLoop`, `BudgetPolicy`, `MemoryRuntime` and
`StaticPromptBuilder` now read. `AgentConfig` is its own provider, so code that builds these
services with a config keeps working.
- `AgentConfig::$platform` and `$memoryPlatform`: the turn's and the memory's providers are
`Bridge\Symfony\RoutedProvider`, which picks the adapter from the platform in force on each
call. The judge's is picked when its service is built, as before.
- `Provider\ApiKeyResolverInterface`: a runtime key per platform. Each adapter calls through a
`Provider\Platform\KeyedPlatform`, which `KeyedPlatformPass` lets rebuild the configured
platform (same factory, HTTP client, catalog and cache settings) with that key.
- `Budget\SpendScopeResolverInterface` and `AgentConfig::$scopeDailyBudgetUsd`: a call is
recorded under a scope (`SpendEntry::$scope`, new column and index) and a scope can have its own
day cap. `SpendLedgerInterface` gains `scopeSpend()` and a `$scope` argument on `record()`.
- Versions of what people edit: `Content\VersionStoreInterface`, in memory or over Doctrine
(`orm.classes.content_version`, optional, extending `Bridge\Doctrine\Model\ContentVersion`),
with drafts, publications and "back to the default". `Rule\VersionedRuleSource` and
`Knowledge\VersionedKnowledgeSource` lay the published rules and documents over the configured
ones; `RuleBook`, `GuideIndex` and the cached prompt are built again when the store's revision
changes.
- `Session\CurrentSession`: the session a turn or a memory extraction runs for, for a host's
settings where there is no request.
- `ProviderRegistry::platforms()`: the platforms with an adapter installed.
- `Memory\MemoryRetentionInterface`: the memory retention a host sets at runtime over
`memory.retention_days`; `agent:prune` uses it.
- `text_before_tool_calls` capability (`ProviderCapabilities::$textBeforeToolCalls`, true by
default, false for Claude Sonnet 5.5, Opus 5.5 and Fable 5): these models return a note longer
than a sentence or two written before a tool call as a progress update inside a thinking
Expand Down Expand Up @@ -77,6 +106,15 @@ All notable changes to this project are documented here. The format is based on

### Changed

- `identity.scope` and `AgentConfig::$scope` are removed: what the assistant stays within comes
from the capabilities (`ScopedCapabilityInterface`). Remove the key from the configuration.
- `StaticPromptBuilder` keeps its text per identity, capability fragments, rules and guides,
instead of once per process.
- No exclamation marks or emoji only on turns that add, stage or confirm; elsewhere the voice
decides. It applied to every turn.
- The configured rules and the Markdown files are no longer tagged themselves: the versioned
sources wrap them (`odiseo_ai_conversational_agent.rule.versioned_source`,
`odiseo_ai_conversational_agent.knowledge.versioned_source`).
- Every port interface ends in `Interface`, like the model interfaces already did:
`CapabilityInterface`, `ContextProviderInterface`, `ConversationInitializerInterface`,
`ClientKeyResolverInterface`, `SpendLedgerInterface`, `EvalEnvironmentInterface`,
Expand Down
52 changes: 46 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ odiseo_ai_conversational_agent:
message: App\Entity\AgentMessage
memory_fact: App\Entity\AgentMemoryFact
spend_entry: App\Entity\AgentSpendEntry
# Optional: rules and documents edited at runtime (see Runtime changes).
content_version: App\Entity\AgentContentVersion
```

A conversation the core creates carries the session id, the principal and the state document.
Expand All @@ -150,9 +152,13 @@ Every service is registered as `odiseo_ai_conversational_agent.*` with explicit
The classes and ports a host uses are aliases, and a host replaces a port by redefining its
alias: `ContextProviderInterface`, `PrincipalResolverInterface`, `TurnHookInterface`,
`ConsoleEnvironmentInterface`, `ConversationInitializerInterface`, `SessionStore`,
`MemoryStoreInterface` and `SpendLedgerInterface`. Capabilities implement `CapabilityInterface`
`MemoryStoreInterface`, `SpendLedgerInterface`, `ConfigOverridesInterface`,
`ApiKeyResolverInterface`, `SpendScopeResolverInterface` and `VersionStoreInterface`. Capabilities implement `CapabilityInterface`
and are collected by the `odiseo_ai_conversational_agent.capability` tag; rule sources, knowledge
sources and knowledge searches are collected the same way.
sources and knowledge searches are collected the same way. A capability that also implements
`ScopedCapabilityInterface` names an area of what the assistant does ("orders"); the prompt's
"Stay within …" line lists the registered areas by priority, so registering a capability widens
it. `identity.audience` completes "talking with …".

### Rules

Expand Down Expand Up @@ -184,9 +190,42 @@ odiseo_ai_conversational_agent:
logged on the `conversational_agent` channel.
- A rule is behaviour, never data that changes (a price, an amount): changing a rule changes the
cached prompt, so the next request reads it uncached.
- Another source (a database, say) implements `Rule\RuleSourceInterface`; autoconfigured services
are tagged `odiseo_ai_conversational_agent.rule_source` and their rules are added to the
configured ones. A source is read once per process.
- Another source implements `Rule\RuleSourceInterface`; autoconfigured services are tagged
`odiseo_ai_conversational_agent.rule_source` and their rules are added to the configured ones.
A source is read again only when something is published (see Runtime changes).
- A rule published at runtime replaces the configured one with its id (see Runtime changes).

### Runtime changes

What a host lets people change without a deploy (an admin screen, say) reaches the agent
through three ports and a version store. Nothing changes until the host implements one.

- **Settings**: `Config\ConfigOverridesInterface::changes()` returns `AgentConfig` arguments to
lay over the configuration for the current request: the identity, `model` and `platform`,
`thinkingEffort`, `memoryModel` and `memoryPlatform`, the budgets. The services that run a turn
read `Config\AgentConfigProviderInterface` (`RuntimeAgentConfig`), so a model saved at runtime
takes over on the next turn and the turn's and memory's providers follow its platform. A key
left out keeps the configured value; a bad value (an unknown effort) is dropped and logged.
- **API keys**: `Provider\ApiKeyResolverInterface::apiKey($platform)` returns another key for a
platform; the adapter then calls through a platform built like the configured one with that
key (`KeyedPlatform`). Null keeps the configured key.
- **Spend scope**: `Budget\SpendScopeResolverInterface::scope()` names the part of the deployment
a call belongs to (one site of several). The ledger records it, and
`scopeDailyBudgetUsd` caps that scope's day on top of the deployment's day cap.
- **The session in progress**: `Session\CurrentSession` holds the session a turn or a memory
extraction runs for, so a host's settings can follow it where there is no request (a queue
worker).
- **Versions**: `Content\VersionStoreInterface` keeps what people edit as versions, by kind and
key: a draft the agent never reads, publications that are only added, and one "back to the
default" version that leaves the configured item in force again. With an
`orm.classes.content_version` entity (extending `Bridge\Doctrine\Model\ContentVersion`) they
persist; without one they are in memory and only the configuration applies.
`Rule\VersionedRuleSource` and `Knowledge\VersionedKnowledgeSource` lay the published versions
of `rule` and `knowledge` over the configured rules and the Markdown files: a version replaces
the item with its id, a disabled one turns it off, and one that only exists as versions is
added. The store's revision changes on every publication, and the house rules, the guide index
and the cached prompt are built again when it does, in every process. Each change costs one
uncached read of the prompt.

### Knowledge

Expand Down Expand Up @@ -331,7 +370,8 @@ Two clocks. `sessions.retention_days` (30) is how long a session is served after
activity; past it the widget starts a new one, but the conversation stays for the host to read.
`conversations.retention_days` (180) is how long it is kept; `memory.retention_days` (180)
does the same for memory facts, counted from their last update, and a fact past it is no longer
used even before it is pruned. Null keeps them. `bin/console agent:prune` applies both,
used even before it is pruned. Null keeps them. A host that sets the memory retention at runtime
says which one pruning uses (`Memory\MemoryRetentionInterface`). `bin/console agent:prune` applies both,
dropping the conversations with their messages and spend; `--dry-run` only counts. Run it
from a daily cron.

Expand Down
24 changes: 24 additions & 0 deletions config/orm/ContentVersion.orm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<mapped-superclass name="Odiseo\AiConversationalAgentBundle\Bridge\Doctrine\Model\ContentVersion">
<unique-constraints>
<unique-constraint name="uniq_agent_content_version" columns="kind,item_key,version_number" />
</unique-constraints>
<indexes>
<index name="idx_agent_content_version_kind" columns="kind,version_number" />
</indexes>
<id name="id" column="id" type="integer">
<generator strategy="AUTO" />
</id>
<field name="kind" column="kind" type="string" length="32" />
<field name="itemKey" column="item_key" type="string" length="128" />
<field name="number" column="version_number" type="integer" />
<field name="data" column="data" type="json" nullable="true" />
<field name="author" column="author" type="string" length="255" nullable="true" />
<field name="note" column="note" type="text" nullable="true" />
<field name="createdAt" column="created_at" type="datetime_immutable" />
</mapped-superclass>
</doctrine-mapping>
2 changes: 2 additions & 0 deletions config/orm/SpendEntry.orm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<index name="idx_agent_spend_session" columns="session_id" />
<index name="idx_agent_spend_day" columns="spent_on" />
<index name="idx_agent_spend_client" columns="client_key,spent_on" />
<index name="idx_agent_spend_scope" columns="scope,spent_on" />
</indexes>

<id name="id" column="id" type="integer">
Expand All @@ -20,6 +21,7 @@
<field name="spentOn" column="spent_on" type="date_immutable" />
<field name="usd" column="usd" type="decimal" precision="12" scale="6" />
<field name="model" column="model" type="string" length="64" nullable="true" />
<field name="scope" column="scope" type="string" length="64" nullable="true" />
<field name="inputTokens" column="input_tokens" type="integer" />
<field name="outputTokens" column="output_tokens" type="integer" />
<field name="cacheCreationTokens" column="cache_creation_tokens" type="integer" />
Expand Down
Loading
Loading