Skip to content
Open
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
17 changes: 13 additions & 4 deletions dice/src/main/resources/prompts/suggest_entities.jinja
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
You are analyzing sources.

{% if context.directions %}
Consider the following direction: {{ context.directions }}
{# `directions` is a prompt variable, not a context field — SourceAnalysisContext
spreads promptVariables at the top level of the model, so this reads whatever a
caller supplied and stays silent when none did. `context.directions` was a
vestige of a field that no longer exists, so this block never rendered. #}
{% if directions %}
Consider the following direction: {{ directions }}
{% endif %}

Given the following text, identify and summarize all entities mentioned.
Include the entity id only if it's provided in the text as a UUID, not a name.
Entity types must only come from the following list:

{# `ownLabel`, not `clazz.simpleName`: `clazz` exists only on JvmType, so every
DynamicType — the schema-declared types, which are most of them in a configured
world — rendered as an empty name in a list the model is told to choose from.
For a JvmType, ownLabel IS the simple name, and it is the key the extracted
labels are matched back against. #}
{% for domainType in context.schema.domainTypes %}
- {{ domainType.clazz.simpleName }}: {{ domainType.description }}
Parents: {{ domainType.parents | map(attribute='clazz') | map(attribute='simpleName') | join(', ') }}
- {{ domainType.ownLabel }}: {{ domainType.description }}
Parents: {{ domainType.parents | map(attribute='ownLabel') | join(', ') }}
Properties:
{% for property in domainType.properties %}
- {{ property.name }} ({{ property.cardinality }}): {{ property.description }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2024-2026 Embabel Pty Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.embabel.dice.entity

import com.embabel.agent.core.Cardinality
import com.embabel.agent.core.ContextId
import com.embabel.agent.core.DataDictionary
import com.embabel.agent.core.DynamicType
import com.embabel.agent.core.ValuePropertyDefinition
import com.embabel.agent.rag.model.Chunk
import com.embabel.common.textio.template.JinjaProperties
import com.embabel.common.textio.template.JinjavaTemplateRenderer
import com.embabel.dice.common.SourceAnalysisContext
import com.embabel.dice.common.resolver.AlwaysCreateEntityResolver
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.core.io.DefaultResourceLoader

/**
* `suggest_entities` renders the list of types the model is told to choose from.
*
* The regression these pin: the template read `domainType.clazz.simpleName`, and `clazz` exists
* only on `JvmType`. Every [DynamicType] — the schema-declared types, which are most of them in a
* configured world — therefore rendered with an EMPTY name under "Entity types must only come from
* the following list", while Jinjava logged one "Cannot resolve property 'clazz'" per type per
* chunk. A prompt that names none of its types cannot be obeyed.
*/
class SuggestEntitiesTemplateTest {

private lateinit var renderer: JinjavaTemplateRenderer

@BeforeEach
fun setUp() {
renderer = JinjavaTemplateRenderer(
jinja = JinjaProperties(prefix = "classpath:/prompts/", suffix = ".jinja"),
resourceLoader = DefaultResourceLoader(),
)
}

private val meeting = DynamicType(
name = "calendar.meeting",
description = "A calendar event surfaced as a signal",
ownProperties = listOf(
ValuePropertyDefinition(
name = "subject",
type = "string",
cardinality = Cardinality.ONE,
description = "Event title",
),
),
)

private fun render(extra: Map<String, Any> = emptyMap()): String {
val context = SourceAnalysisContext(
schema = DataDictionary.fromDomainTypes("test", listOf(meeting)),
entityResolver = AlwaysCreateEntityResolver,
contextId = ContextId("test-context"),
)
return renderer.renderLoadedTemplate(
"suggest_entities",
mapOf(
"context" to context,
"chunk" to Chunk.create(text = "Test chunk text", parentId = "source-1"),
) + extra,
)
}

@Test
fun `a dynamic type is named in the list of permitted types`() {
val result = render()
assertTrue(result.contains("Meeting"), "the type's label is rendered, in:\n$result")
assertTrue(result.contains("A calendar event surfaced as a signal"), "its description too")
assertTrue(result.contains("subject"), "and its properties")
}

@Test
fun `directions render when a caller supplies them and are silent otherwise`() {
assertFalse(
render().contains("Consider the following direction"),
"no directions supplied — the block stays out of the prompt",
)
assertTrue(
render(mapOf("directions" to "focus on scheduling"))
.contains("Consider the following direction: focus on scheduling"),
)
}
}
102 changes: 102 additions & 0 deletions specs/dice-and-coala.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# DICE and CoALA

How DICE relates to the CoALA cognitive-architecture framework, and how to use DICE as the
long-term memory engine inside a CoALA-based agent architecture.

## The short answer

"Why GUM instead of CoALA?" is a category error. The two operate at different levels:

- **CoALA** ([Sumers, Yao, Narasimhan & Griffiths, arXiv:2309.02427](https://arxiv.org/abs/2309.02427),
TMLR 2024) is a **cognitive-architecture framework**: a taxonomy that organizes a language agent
around working memory, three kinds of long-term memory (episodic, semantic, procedural), an action
space (internal reasoning/retrieval/learning actions, external grounding actions), and a
decision-making loop. It tells you *what subsystems an agent should have*. It deliberately does
not specify mechanisms — how propositions are extracted, deduplicated, revised, decayed,
consolidated, or audited.
- **GUM** ([Shaikh et al., arXiv:2505.10831](https://arxiv.org/abs/2505.10831), Stanford/Microsoft
2025) is a **concrete, empirically validated mechanism** for one of those subsystems: building a
semantic user model from confidence-weighted propositions, with published accuracy results
(76% overall, 100% for high-confidence propositions).

DICE did not choose GUM *over* CoALA. It chose GUM as the implementation mechanism for the
long-term memory component that a CoALA-style architecture calls for. An organization
standardizing on CoALA as its architectural vocabulary still needs a concrete engine behind each
memory box in the diagram. DICE is that engine for long-term memory — designed for the JVM,
integrated with existing enterprise entities, and auditable.

## Mapping DICE onto CoALA's memory taxonomy

DICE classifies every proposition with a `KnowledgeType` that corresponds one-to-one with CoALA's
memory taxonomy:

| CoALA memory | DICE `KnowledgeType` | DICE mechanism |
|---|---|---|
| **Semantic** — facts about the world and the user | `SEMANTIC` | The core proposition store: confidence-weighted, entity-resolved, revised through five-way classification, decaying over time. Low decay, long-lived. |
| **Episodic** — records of the agent's experiences | `EPISODIC` | Propositions with temporal context and higher decay, plus the **grounding chain**: every proposition links back to the source chunks it was extracted from, so the raw experience record is retained alongside the distilled knowledge. |
| **Procedural** — how things are done | `PROCEDURAL` | Declarative statements of preference, habit, and rule ("prefers X", "when deploying, use Y") that steer agent behaviour. Executable skills and code remain the agent framework's concern (see below). |
| **Working** — current, session-scoped context | `WORKING` | Transient propositions not yet consolidated into long-term memory. Working-memory *management* — the context assembled for each LLM call — belongs to the agent framework, with DICE's memory projection supplying the long-term contribution to it. |

Two CoALA internal action types also have direct DICE counterparts:

- **Retrieval actions** — CoALA's "read from long-term memory into working memory" — are DICE's
projections and query surfaces: vector similarity, canonical match, entity-based lookup,
composable `PropositionQuery`, graph traversal via the Neo4j projection, Prolog inference,
and natural-language QA via the Oracle.
- **Learning actions** — CoALA's "write experience into long-term memory" — are the proposition
pipeline (extract → resolve → revise) plus the consolidation dream loop, which synthesizes
higher-level propositions from lower-level ones (episodic → semantic distillation, with source
tracking through the abstraction hierarchy).

## What CoALA leaves open — and DICE's answers

CoALA is explicit that it is a conceptual framework, not an implementation. Building a real
memory system from it means making mechanism decisions the paper does not make. These are exactly
the decisions DICE takes a position on:

| Open question in a CoALA build | DICE's answer |
|---|---|
| What is a memory unit? | A structured **proposition**: text, typed entity mentions, confidence, decay, grounding, reinforcement count, abstraction level — not a flat fact string. |
| How is new information reconciled with old? | Five-way revision classification (IDENTICAL / SIMILAR / CONTRADICTORY / UNRELATED / GENERALIZES) with outcome-dependent confidence adjustment, batched for throughput, with deterministic fast paths that skip the LLM. |
| What happens on contradiction? | **Both propositions are retained** with reduced confidence — history is never silently deleted. |
| How does memory age? | Effective confidence = `confidence * exp(-decay * k * age_days)` (the GUM formula), so transient knowledge fades and stable knowledge persists, without hard deletion. |
| How does memory connect to existing systems? | A seven-strategy entity-resolution pipeline links mentions to the organization's *existing* entities (any `NamedEntityDataRepository`), rather than creating a parallel entity universe. |
| How is consolidation performed? | Admission gates, mark-and-sweep reclamation with an audit trail, and the consolidation dream loop producing multi-level abstractions with source lineage. |
| How is memory trusted and audited? | Per-proposition provenance (grounding chain to source chunks), source authority and trust scoring, reinforcement counting, and query-time authority filtering. |

## Using DICE inside a CoALA-based architecture

For an organization adopting CoALA as its organizing framework:

1. **Keep CoALA as the architectural vocabulary.** It is a good shared language for what an agent
needs: the memory taxonomy, the action space, the decision loop.
2. **Let the agent framework own the decision loop and working memory.** CoALA locates planning,
action selection, and working-memory management in the agent itself. In the Embabel stack that
is the [Embabel Agent Framework](https://github.com/embabel/embabel-agent) (typed actions,
goal-oriented planning, blackboard state); DICE deliberately does not duplicate it.
3. **Use DICE as the long-term memory engine** behind the semantic, episodic, and (declarative)
procedural boxes, with the memory projection feeding the long-term contribution to working
context.
4. **Exploit the audit properties in regulated environments.** Because contradictions are retained
rather than deleted, every proposition is grounded in source evidence, and confidence evolution
is explicit, the question "why does the system believe X, and how sure is it?" has an
inspectable answer. Memory systems that hard-delete on conflict cannot reconstruct that history.
5. **Deploy inside the perimeter.** DICE is an embeddable JVM library with no mandatory cloud
service or graph-database dependency — relevant where data residency and vendor-risk review
constrain managed memory services.

## What DICE does not attempt

Honesty about scope, in CoALA's terms:

- **The decision loop.** DICE is memory, not an agent. Planning, action selection, and grounding
actions belong to the agent framework.
- **Working-memory management.** DICE classifies and stores working-type knowledge and supplies
retrieval into context, but assembling each LLM call's context window is the caller's job.
- **Procedural memory as executable skills.** CoALA's procedural memory includes the agent's own
code and learned skills. DICE covers the declarative slice (preferences, habits, rules as
propositions); skill acquisition and storage are out of scope.

For how DICE compares against other *memory implementations* (Zep/Graphiti, Mem0, LangMem, the
Google/AWS/Microsoft managed offerings, Neo4j Agent Memory, LiveGraph), see
[competitive-positioning.md](competitive-positioning.md).