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
19 changes: 19 additions & 0 deletions golem-xiv-api-backend/src/main/kotlin/CognitionApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.xemantic.ai.golem.api.backend

import com.xemantic.ai.golem.api.CognitionListItem
import com.xemantic.ai.golem.api.EpistemicAgent
import com.xemantic.ai.golem.api.PhenomenalExpression
import com.xemantic.ai.golem.api.Phenomenon
Expand Down Expand Up @@ -111,6 +112,15 @@ interface CognitionRepository {
cognitionId: Long
): Phenomenon.Intent?

suspend fun listCognitions(
limit: Int = 50,
offset: Int = 0
): List<CognitionListItem>

suspend fun isFirstExpression(
cognitionId: Long
): Boolean

}

interface CognitiveMemory {
Expand Down Expand Up @@ -178,6 +188,15 @@ interface CognitiveMemory {
type: StorageType
): String

suspend fun listCognitions(
limit: Int = 50,
offset: Int = 0
): List<CognitionListItem>

suspend fun getExpressionCount(
cognitionId: Long
): Int

}

data class CulminatedWithIntent(
Expand Down
25 changes: 25 additions & 0 deletions golem-xiv-api-backend/src/main/kotlin/TitleGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Golem XIV - Autonomous metacognitive AI system with semantic memory and self-directed research
* Copyright (C) 2025 Kazimierz Pogoda / Xemantic
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.xemantic.ai.golem.api.backend

interface TitleGenerator {

suspend fun generateTitle(userMessage: String): String

}
6 changes: 6 additions & 0 deletions golem-xiv-api-client/src/commonMain/kotlin/GolemServices.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.xemantic.ai.golem.api.client

import com.xemantic.ai.golem.api.CognitionListItem
import com.xemantic.ai.golem.api.GolemError
import com.xemantic.ai.golem.api.Phenomenon

Expand Down Expand Up @@ -51,6 +52,11 @@ interface CognitionService {
phenomena: List<Phenomenon>
)

suspend fun listCognitions(
limit: Int = 50,
offset: Int = 0
): List<CognitionListItem>

}

// TODO should be rather exception hierarchy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.xemantic.ai.golem.api.client.http

import com.xemantic.ai.golem.api.CognitionListItem
import com.xemantic.ai.golem.api.Phenomenon
import com.xemantic.ai.golem.api.client.CognitionService
import io.ktor.client.HttpClient
Expand Down Expand Up @@ -47,4 +48,11 @@ class HttpClientCognitionService(
)
}

override suspend fun listCognitions(
limit: Int,
offset: Int
): List<CognitionListItem> = client.serviceGet(
uri = "/api/cognitions?limit=$limit&offset=$offset"
)

}
7 changes: 7 additions & 0 deletions golem-xiv-api/src/commonMain/kotlin/Cognition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,10 @@ sealed interface Phenomenon {
) : Phenomenon

}

@Serializable
data class CognitionListItem(
val id: Long,
val title: String?,
val initiationMoment: Instant
)
7 changes: 7 additions & 0 deletions golem-xiv-api/src/commonMain/kotlin/GolemOutput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,11 @@ sealed interface GolemOutput {
val event: CognitionEvent
) : GolemOutput, WithCognitionId

@Serializable
@SerialName("CognitionTitleUpdated")
data class CognitionTitleUpdated(
override val cognitionId: Long,
val title: String
) : GolemOutput, WithCognitionId

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Golem XIV - Autonomous metacognitive AI system with semantic memory and self-directed research
* Copyright (C) 2025 Kazimierz Pogoda / Xemantic
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.xemantic.ai.golem.cognizer.anthropic

import com.xemantic.ai.anthropic.Anthropic
import com.xemantic.ai.anthropic.content.Text
import com.xemantic.ai.anthropic.message.Message
import com.xemantic.ai.anthropic.message.Role
import com.xemantic.ai.golem.api.backend.TitleGenerator

class AnthropicTitleGenerator(
private val anthropic: Anthropic
) : TitleGenerator {

override suspend fun generateTitle(userMessage: String): String {
val response = anthropic.messages.create {
maxTokens = 30
messages = listOf(
Message {
role = Role.USER
content = listOf(Text("""
Generate a concise title (max 6 words) for a conversation starting with:

"$userMessage"

Reply with only the title, no quotes or extra punctuation.
""".trimIndent()))
}
)
}
return (response.content.first() as Text).text.trim()
}

}
24 changes: 24 additions & 0 deletions golem-xiv-core/src/main/kotlin/GolemXiv.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@

package com.xemantic.ai.golem.core

import com.xemantic.ai.golem.api.CognitionListItem
import com.xemantic.ai.golem.api.Phenomenon
import com.xemantic.ai.golem.api.CognitionEvent
import com.xemantic.ai.golem.api.EpistemicAgent
import com.xemantic.ai.golem.api.GolemOutput
import com.xemantic.ai.golem.api.backend.CognitionRepository
import com.xemantic.ai.golem.api.backend.Cognizer
import com.xemantic.ai.golem.api.backend.AgentIdentity
import com.xemantic.ai.golem.api.backend.TitleGenerator
import com.xemantic.ai.golem.api.backend.script.ExecuteGolemScript
import com.xemantic.ai.golem.core.kotlin.getClasspathResource
import com.xemantic.ai.golem.core.script.GolemScriptExecutor
Expand Down Expand Up @@ -55,6 +57,7 @@ class GolemXiv(
private val identity: AgentIdentity,
private val repository: CognitionRepository,
private val cognizer: Cognizer,
private val titleGenerator: TitleGenerator?,
private val golemScriptDependencyProvider: GolemScriptDependencyProvider,
private val outputs: FlowCollector<GolemOutput>
) : AutoCloseable {
Expand Down Expand Up @@ -84,6 +87,11 @@ class GolemXiv(
return info.id
}

suspend fun listCognitions(
limit: Int = 50,
offset: Int = 0
): List<CognitionListItem> = repository.listCognitions(limit, offset)

/**
* @throws com.xemantic.ai.golem.api.backend.GolemException
*/
Expand All @@ -107,6 +115,22 @@ class GolemXiv(

cognitionBroadcaster.emit(phenomenalExpression)

// Generate title for first user message
if (titleGenerator != null && repository.isFirstExpression(cognitionId)) {
val firstText = phenomena.filterIsInstance<Phenomenon.Text>().firstOrNull()
if (firstText != null) {
scope.launch {
try {
val title = titleGenerator.generateTitle(firstText.text)
repository.getCognition(cognitionId).setTitle(title)
outputs.emit(GolemOutput.CognitionTitleUpdated(cognitionId, title))
} catch (e: Exception) {
logger.warn(e) { "Failed to generate title for cognition $cognitionId" }
}
}
}
}

activeCognitionMap[cognitionId] = scope.launch {

logger.debug { "Cognition[$cognitionId]: Initiating cognition" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.xemantic.ai.golem.core.cognition

import com.xemantic.ai.golem.api.CognitionListItem
import com.xemantic.ai.golem.api.EpistemicAgent
import com.xemantic.ai.golem.api.PhenomenalExpression
import com.xemantic.ai.golem.api.Phenomenon
Expand Down Expand Up @@ -307,4 +308,13 @@ class DefaultCognitionRepository(
}
}

override suspend fun listCognitions(
limit: Int,
offset: Int
): List<CognitionListItem> = memory.listCognitions(limit, offset)

override suspend fun isFirstExpression(
cognitionId: Long
): Boolean = memory.getExpressionCount(cognitionId) == 1

}
43 changes: 43 additions & 0 deletions golem-xiv-neo4j/src/main/kotlin/Neo4jCognitiveMemory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.xemantic.ai.golem.neo4j

import com.xemantic.ai.golem.api.CognitionListItem
import com.xemantic.ai.golem.api.EpistemicAgent
import com.xemantic.ai.golem.api.PhenomenalExpression
import com.xemantic.ai.golem.api.Phenomenon
Expand All @@ -32,6 +33,7 @@ import com.xemantic.neo4j.driver.singleOrNull
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import org.neo4j.driver.types.Node

class Neo4jCognitiveMemory(
Expand Down Expand Up @@ -461,6 +463,47 @@ class Neo4jCognitiveMemory(
}
}

override suspend fun listCognitions(
limit: Int,
offset: Int
): List<CognitionListItem> = neo4j.read { tx ->
tx.run(
query = $$"""
MATCH (c:Cognition)
WHERE NOT EXISTS { (parent:Cognition)-[:hasChild]->(c) }
RETURN id(c) AS id, c.title AS title, c.initiationMoment AS initiationMoment
ORDER BY c.initiationMoment DESC
SKIP $offset
LIMIT $limit
""".trimIndent(),
parameters = mapOf(
"offset" to offset,
"limit" to limit
)
).records().toList().map { record ->
CognitionListItem(
id = record["id"].asLong(),
title = record["title"]?.let { if (it.isNull) null else it.asString() },
initiationMoment = record["initiationMoment"].asInstant()
)
}
}

override suspend fun getExpressionCount(
cognitionId: Long
): Int = neo4j.read { tx ->
tx.run(
query = $$"""
MATCH (c:Cognition)-[:hasPart]->(e:PhenomenalExpression)
WHERE id(c) = $cognitionId
RETURN count(e) AS count
""".trimIndent(),
parameters = mapOf(
"cognitionId" to cognitionId
)
).single()["count"].asInt()
}

}

private fun StorageType.toPropertyName(): String = when (this) {
Expand Down
Loading
Loading