libertaria-stack/sdk/janus-sdk/libertaria/context.jan

171 lines
5.2 KiB
Plaintext

-- libertaria/context.jan
-- NCP (Nexus Context Protocol) implementation
-- Structured, hierarchical context management for agent conversations
module Context exposing
( Context
, create, fork, merge, close
, current_depth, max_depth
, add_message, get_messages
, subscribe, unsubscribe
, to_astdb_query
)
import message.{Message}
import memory.{VectorStore}
import time.{timestamp}
-- Context is a structured conversation container
type Context =
{ id: context_id.ContextId
, parent: ?context_id.ContextId -- Hierarchical nesting
, depth: int -- Nesting level (prevents infinite loops)
, created_at: timestamp.Timestamp
, messages: list.List(Message)
, metadata: metadata.ContextMetadata
, vector_store: ?VectorStore -- Semantic indexing
, subscribers: set.Set(fingerprint.Fingerprint)
, closed: bool
}
type ContextConfig =
{ max_depth: int = 100 -- Max nesting before forced flattening
, max_messages: int = 10000 -- Auto-archive older messages
, enable_vector_index: bool = true
, retention_policy: RetentionPolicy
}
type RetentionPolicy =
| Keep_Forever
| Auto_Archive_After(duration.Duration)
| Delete_After(duration.Duration)
-- Create root context
-- Top-level conversation container
fn create(config: ContextConfig) -> Context
let id = context_id.generate()
let now = timestamp.now()
let vs = if config.enable_vector_index
then some(memory.create_vector_store())
else null
{ id = id
, parent = null
, depth = 0
, created_at = now
, messages = list.empty()
, metadata = metadata.create(id)
, vector_store = vs
, subscribers = set.empty()
, closed = false
}
-- Fork child context from parent
-- Used for: sub-conversations, branching decisions, isolated experiments
fn fork(parent: Context, reason: string, config: ContextConfig) -> result.Result(Context, error.ForkError)
if parent.depth >= config.max_depth then
error.err(MaxDepthExceeded)
else if parent.closed then
error.err(ParentClosed)
else
let id = context_id.generate()
let now = timestamp.now()
let vs = if config.enable_vector_index
then some(memory.create_vector_store())
else null
let child =
{ id = id
, parent = some(parent.id)
, depth = parent.depth + 1
, created_at = now
, messages = list.empty()
, metadata = metadata.create(id)
|> metadata.set_parent(parent.id)
|> metadata.set_fork_reason(reason)
, vector_store = vs
, subscribers = set.empty()
, closed = false
}
ok(child)
-- Merge child context back into parent
-- Consolidates messages, preserves fork history
fn merge(child: Context, into parent: Context) -> result.Result(Context, error.MergeError)
if child.parent != some(parent.id) then
error.err(NotMyParent)
else if child.closed then
error.err(ChildClosed)
else
let merged_messages = parent.messages ++ child.messages
let merged_subs = set.union(parent.subscribers, child.subscribers)
let updated_parent =
{ parent with
messages = merged_messages
, subscribers = merged_subs
, metadata = parent.metadata
|> metadata.add_merge_history(child.id, child.messages.length())
}
ok(updated_parent)
-- Close context (final state)
-- No more messages, preserves history
fn close(ctx: Context) -> Context
{ ctx with closed = true }
-- Get current nesting depth
fn current_depth(ctx: Context) -> int
ctx.depth
-- Get max allowed depth (from config)
fn max_depth(ctx: Context) -> int
ctx.metadata.config.max_depth
-- Add message to context
-- Indexes in vector store if enabled
fn add_message(ctx: Context, msg: Message) -> result.Result(Context, error.AddError)
if ctx.closed then
error.err(ContextClosed)
else
let updated = { ctx with messages = ctx.messages ++ [msg] }
-- Index in vector store for semantic search
match ctx.vector_store with
| null -> ok(updated)
| some(vs) ->
let embedding = memory.embed(message.content(msg))
let indexed_vs = memory.store(vs, message.id(msg), embedding)
ok({ updated with vector_store = some(indexed_vs) })
-- Get all messages in context
fn get_messages(ctx: Context) -> list.List(Message)
ctx.messages
-- Subscribe agent to context updates
fn subscribe(ctx: Context, agent: fingerprint.Fingerprint) -> Context
{ ctx with subscribers = set.insert(ctx.subscribers, agent) }
-- Unsubscribe agent
fn unsubscribe(ctx: Context, agent: fingerprint.Fingerprint) -> Context
{ ctx with subscribers = set.remove(ctx.subscribers, agent) }
-- Convert to ASTDB query for semantic search
-- Enables: "Find similar contexts", "What did we discuss about X?"
fn to_astdb_query(ctx: Context) -> astdb.Query
let message_hashes = list.map(ctx.messages, message.hash)
let time_range =
{ start = ctx.created_at
, end = match list.last(ctx.messages) with
| null -> timestamp.now()
| some(last_msg) -> message.timestamp(last_msg)
}
astdb.query()
|> astdb.with_context_id(ctx.id)
|> astdb.with_message_hashes(message_hashes)
|> astdb.with_time_range(time_range)
|> astdb.with_depth(ctx.depth)