feat: upgrade Domovoy local cognition and recovery #87

Merged
m merged 5 commits from codex/creator-chat-model-status-v1 into main 2026-08-21 22:48:18 +00:00
15 changed files with 240 additions and 22 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ func (s discoveryService) handler(w http.ResponseWriter, r *http.Request) {
return
}
discovered, probe := registration.Discovery.Discover(r.Context())
snapshot := capability.CognitiveDiscoverySnapshot{ProviderInstanceID: registration.ProviderInstanceID, ProviderKind: registration.Kind, EndpointRef: registration.EndpointRef, CredentialRef: registration.CredentialRef, AuthMode: registration.AuthMode, BillingMode: registration.BillingMode, Enabled: true, OwnerPrincipalID: s.owner, CapabilityID: creatorAdvisoryCapabilityID, DataPolicy: capability.DataPolicyLocalOnly, LimitPolicyRef: "creator-advisory-local-v1", Health: probe.Health, FailureClass: capability.CognitiveFailureType(probe.Failure), ObservedAtUnixMillis: s.now().UnixMilli()}
snapshot := capability.CognitiveDiscoverySnapshot{ProviderInstanceID: registration.ProviderInstanceID, ProviderKind: registration.Kind, EndpointRef: registration.EndpointRef, CredentialRef: registration.CredentialRef, AuthMode: registration.AuthMode, BillingMode: registration.BillingMode, Enabled: true, OwnerPrincipalID: s.owner, CapabilityID: creatorAdvisoryCapabilityID, DataPolicy: registration.DataPolicy, LimitPolicyRef: "creator-advisory-local-v1", Health: probe.Health, FailureClass: capability.CognitiveFailureType(probe.Failure), ObservedAtUnixMillis: s.now().UnixMilli()}
for _, model := range discovered {
snapshot.Models = append(snapshot.Models, capability.DiscoveredModelSnapshot{ProviderModelID: model.ProviderModelID, Fingerprint: model.Fingerprint, MaxContextBytes: model.MaxContextBytes, Modalities: model.Modalities, Capabilities: model.Capabilities, Installed: capability.OperationalYes, Loaded: capability.OperationalNotObserved, Invokable: capability.OperationalNotObserved})
}
+43 -3
View File
@@ -32,6 +32,7 @@ type providerRegistration struct {
CredentialRef capability.CredentialRef
AuthMode capability.AuthMode
BillingMode capability.BillingMode
DataPolicy capability.DataPolicy
Provider advisoryProvider
Discovery models.LocalProvider
}
@@ -39,6 +40,8 @@ type providerRegistration struct {
type cognitiveGateway struct {
resolver bindingResolver
providers map[capability.ProviderKind][]providerRegistration
builtinLocal *providerRegistration
externalRecovery []providerRegistration
now func() time.Time
allowCanary bool
canaryMaxOutputBytes int
@@ -77,7 +80,7 @@ func (g cognitiveGateway) advisoryHandler(w http.ResponseWriter, r *http.Request
requirement := capability.CognitiveRequirement{CapabilityID: creatorAdvisoryCapabilityID, OwnerPrincipalID: request.PrincipalID, DataPolicy: request.DataPolicy, RequiredModalities: []capability.ModelModality{capability.ModalityText}, RequiredCapabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, AllowCanary: g.allowCanary, InputBytes: int64(len(request.Message)), MaxOutputBytes: int64(maxOutputBytes), Objective: selectionObjectiveForMeaning(request.Meaning, request.Message)}
selected, err := g.resolver.Resolve(r.Context(), requirement)
if err != nil {
writeGatewayResponse(w, builtinAdvisory(request))
writeGatewayResponse(w, g.invokeBuiltinAdvisory(r.Context(), request, maxOutputBytes))
return
}
if selected.Binding.DataPolicy != capability.DataPolicyLocalOnly && !request.EgressAcknowledged {
@@ -86,7 +89,7 @@ func (g cognitiveGateway) advisoryHandler(w http.ResponseWriter, r *http.Request
}
provider := g.provider(selected)
if provider == nil {
writeGatewayResponse(w, builtinAdvisory(request))
writeGatewayResponse(w, g.invokeBuiltinAdvisory(r.Context(), request, maxOutputBytes))
return
}
invocation := cognitive.AdvisoryRequest{Version: request.Version, RequestID: request.RequestID, ActorID: request.ActorID, PrincipalID: request.PrincipalID, ScopeID: request.ScopeID, Message: request.Message, Context: request.Context, Locale: request.Locale, MaxOutputBytes: maxOutputBytes, ProviderBindingID: selected.Binding.ID, ProviderBindingRevision: selected.Binding.Revision, ProviderInstanceID: selected.Provider.ID, ModelDescriptorID: selected.Model.ID, ProviderModelID: selected.Model.ProviderModelID}
@@ -114,7 +117,7 @@ func (g cognitiveGateway) advisoryHandler(w http.ResponseWriter, r *http.Request
}
}
if response.FinishReason != cognitive.FinishCompleted && fallbackEligible(response.FinishReason) {
writeGatewayResponse(w, builtinAdvisory(request))
writeGatewayResponse(w, g.invokeBuiltinAdvisory(r.Context(), request, maxOutputBytes))
return
}
writeGatewayResponse(w, cognitive.GatewayAdvisoryResponse{Version: response.Version, RequestID: response.RequestID, AdvisoryText: response.AdvisoryText, ReferencedExcerptIDs: response.ReferencedExcerptIDs, FinishReason: response.FinishReason, Usage: response.Usage})
@@ -164,6 +167,43 @@ func builtinAdvisory(request cognitive.GatewayAdvisoryRequest) cognitive.Gateway
return cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, AdvisoryText: text, FinishReason: cognitive.FinishCompleted}
}
func (g cognitiveGateway) invokeBuiltinAdvisory(ctx context.Context, request cognitive.GatewayAdvisoryRequest, maxOutputBytes int) cognitive.GatewayAdvisoryResponse {
if g.builtinLocal != nil && g.builtinLocal.Provider != nil {
invocation := cognitive.AdvisoryRequest{Version: request.Version, RequestID: request.RequestID, ActorID: request.ActorID, PrincipalID: request.PrincipalID, ScopeID: request.ScopeID, Message: request.Message, Context: request.Context, Locale: request.Locale, MaxOutputBytes: maxOutputBytes, ProviderBindingID: "builtin-local-binding-v1", ProviderBindingRevision: 1, ProviderInstanceID: "builtin-local-lifeboat-v1", ModelDescriptorID: "builtin-local-model-v1", ProviderModelID: "domovoy-lifeboat"}
response, err := g.builtinLocal.Provider.Advise(ctx, invocation)
if err == nil && response.FinishReason == cognitive.FinishCompleted && strings.TrimSpace(response.AdvisoryText) != "" {
return cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, AdvisoryText: response.AdvisoryText, ReferencedExcerptIDs: response.ReferencedExcerptIDs, FinishReason: response.FinishReason, Usage: response.Usage}
}
}
if request.EgressAcknowledged {
if response, ok := g.recoverExternalAdvisory(ctx, request, maxOutputBytes); ok {
return response
}
}
return builtinAdvisory(request)
}
func (g cognitiveGateway) recoverExternalAdvisory(ctx context.Context, request cognitive.GatewayAdvisoryRequest, maxOutputBytes int) (cognitive.GatewayAdvisoryResponse, bool) {
for _, candidate := range g.externalRecovery {
if candidate.Provider == nil || candidate.Discovery == nil {
continue
}
probeCtx, cancel := context.WithTimeout(ctx, 8*time.Second)
modelsFound, probe := candidate.Discovery.Discover(probeCtx)
cancel()
if probe.Health != capability.HealthAvailable || len(modelsFound) == 0 {
continue
}
model := modelsFound[0]
invocation := cognitive.AdvisoryRequest{Version: request.Version, RequestID: request.RequestID, ActorID: request.ActorID, PrincipalID: request.PrincipalID, ScopeID: request.ScopeID, Message: request.Message, Context: request.Context, Locale: request.Locale, MaxOutputBytes: maxOutputBytes, ProviderBindingID: "recovery-external-binding-v1", ProviderBindingRevision: 1, ProviderInstanceID: capability.ProviderInstanceID("recovery-" + string(candidate.ProviderInstanceID)), ModelDescriptorID: capability.ModelDescriptorID("recovery-model-" + string(model.ProviderModelID)), ProviderModelID: model.ProviderModelID}
response, err := candidate.Provider.Advise(ctx, invocation)
if err == nil && response.FinishReason == cognitive.FinishCompleted && strings.TrimSpace(response.AdvisoryText) != "" {
return cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, AdvisoryText: response.AdvisoryText, ReferencedExcerptIDs: response.ReferencedExcerptIDs, FinishReason: response.FinishReason, Usage: response.Usage}, true
}
}
return cognitive.GatewayAdvisoryResponse{}, false
}
func (g cognitiveGateway) observe(ctx context.Context, selected capability.ResolvedCognitiveBinding, response cognitive.AdvisoryResponse, started time.Time) {
usage := response.Usage
observationID, idErr := ids.New()
@@ -168,6 +168,19 @@ func TestNoActiveBindingUsesBuiltInProviderWithoutInvocation(t *testing.T) {
}
}
func TestNoActiveBindingInvokesBuiltInLocalProvider(t *testing.T) {
registry := &registryStub{err: errors.New("no active binding")}
provider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", AdvisoryText: "локальный ответ", FinishReason: cognitive.FinishCompleted}}
builtin := providerRegistration{ProviderInstanceID: "local-lifeboat", Kind: "openai-compatible-chat-v1", EndpointRef: "local-lifeboat-v1", Provider: provider}
gateway := cognitiveGateway{resolver: registry, builtinLocal: &builtin, now: time.Now}
request := httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest()))
response := httptest.NewRecorder()
gateway.advisoryHandler(response, request)
if response.Code != http.StatusOK || !provider.called || !strings.Contains(response.Body.String(), "локальный ответ") {
t.Fatalf("status=%d provider_called=%t body=%s", response.Code, provider.called, response.Body.String())
}
}
func TestProviderLossBetweenRoutingAndInvocationFallsBackOnce(t *testing.T) {
primary := gatewaySelection()
primary.Binding.ID = "gpu-binding"
+19 -2
View File
@@ -1,6 +1,7 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -36,12 +37,12 @@ func (g cognitiveGateway) interpretationHandler(w http.ResponseWriter, r *http.R
}
selected, err := g.resolver.Resolve(r.Context(), requirement)
if err != nil {
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable})
writeInterpretationResponse(w, g.interpretWithBuiltin(r.Context(), request, maxOutputBytes))
return
}
provider := g.provider(selected)
if provider == nil {
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable})
writeInterpretationResponse(w, g.interpretWithBuiltin(r.Context(), request, maxOutputBytes))
return
}
invocation := cognitive.AdvisoryRequest{
@@ -77,6 +78,22 @@ func (g cognitiveGateway) interpretationHandler(w http.ResponseWriter, r *http.R
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, Candidate: candidate, FinishReason: cognitive.FinishCompleted, Usage: response.Usage})
}
func (g cognitiveGateway) interpretWithBuiltin(ctx context.Context, request cognitive.GatewayInterpretationRequest, maxOutputBytes int) cognitive.GatewayInterpretationResponse {
if g.builtinLocal == nil || g.builtinLocal.Provider == nil {
return cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable}
}
invocation := cognitive.AdvisoryRequest{Version: request.Version, RequestID: request.RequestID, ActorID: request.ActorID, PrincipalID: request.PrincipalID, ScopeID: request.ScopeID, Message: interpretationPrompt(request), Locale: request.Locale, MaxOutputBytes: maxOutputBytes, ProviderBindingID: "builtin-local-binding-v1", ProviderBindingRevision: 1, ProviderInstanceID: "builtin-local-lifeboat-v1", ModelDescriptorID: "builtin-local-model-v1", ProviderModelID: "domovoy-lifeboat"}
response, err := g.builtinLocal.Provider.Advise(ctx, invocation)
if err != nil || response.FinishReason != cognitive.FinishCompleted {
return cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable}
}
candidate, ok := parseInterpretationCandidate(response.AdvisoryText)
if !ok {
return cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishInvalidStructuredOutput, Usage: response.Usage}
}
return cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, Candidate: candidate, FinishReason: cognitive.FinishCompleted, Usage: response.Usage}
}
func selectionObjectiveForInterpretation(request cognitive.GatewayInterpretationRequest) capability.SelectionObjective {
if request.RequestedOutcome != "" || request.DeterministicApplicability == cognitive.InterpretationNeedsBroader || request.DeterministicApplicability == cognitive.InterpretationMixedIntent || request.DeterministicApplicability == cognitive.InterpretationLowConfidence {
return capability.SelectionQuality
+10 -5
View File
@@ -40,31 +40,36 @@ func main() {
}
providers := map[capability.ProviderKind][]providerRegistration{}
discoveries := map[string]providerRegistration{}
var builtinLocal *providerRegistration
externalRecovery := []providerRegistration{}
cpuAdapter, err := models.NewOpenAICompatibleLocalAdapter(envOr("LOCAL_CPU_PROVIDER_URL", "http://docker.cin.su:18082/v1"), nil)
if err != nil {
log.Fatal(err)
}
cpu := providerRegistration{ProviderInstanceID: "local-cpu-primary", Kind: "openai-compatible-chat-v1", EndpointRef: "local-cpu-primary-v1", AuthMode: capability.AuthNone, BillingMode: capability.BillingFree, Provider: cpuAdapter, Discovery: cpuAdapter}
cpu := providerRegistration{ProviderInstanceID: "local-cpu-primary", Kind: "openai-compatible-chat-v1", EndpointRef: "local-cpu-primary-v1", AuthMode: capability.AuthNone, BillingMode: capability.BillingFree, DataPolicy: capability.DataPolicyLocalOnly, Provider: cpuAdapter, Discovery: cpuAdapter}
providers[cpu.Kind] = append(providers[cpu.Kind], cpu)
discoveries["cpu"] = cpu
if lifeboat, enabled, lifeboatErr := newLifeboatProvider(os.Getenv("LOCAL_LIFEBOAT_PROVIDER_URL")); lifeboatErr != nil {
log.Fatal(lifeboatErr)
} else if enabled {
lifeboat.DataPolicy = capability.DataPolicyLocalOnly
providers[lifeboat.Kind] = append(providers[lifeboat.Kind], lifeboat)
discoveries["lifeboat"] = lifeboat
builtinLocal = &lifeboat
}
gpuAdapter, err := models.NewOllamaAdapter(envOr("LOCAL_GPU_PROVIDER_URL", "http://192.168.220.91:11434"), nil)
if err != nil {
log.Fatal(err)
}
gpu := providerRegistration{ProviderInstanceID: "local-gpu-opportunistic", Kind: "ollama-v1", EndpointRef: "local-gpu-opportunistic-v1", AuthMode: capability.AuthNone, BillingMode: capability.BillingFree, Provider: gpuAdapter, Discovery: gpuAdapter}
gpu := providerRegistration{ProviderInstanceID: "local-gpu-opportunistic", Kind: "ollama-v1", EndpointRef: "local-gpu-opportunistic-v1", AuthMode: capability.AuthNone, BillingMode: capability.BillingFree, DataPolicy: capability.DataPolicyExternalMessageOnly, Provider: gpuAdapter, Discovery: gpuAdapter}
providers[gpu.Kind] = append(providers[gpu.Kind], gpu)
discoveries["gpu"] = gpu
externalRecovery = append(externalRecovery, gpu)
if openAIProvider != nil {
kind := capability.ProviderKind(envOr("OPENAI_PROVIDER_KIND", "openai-responses-v1"))
providers[kind] = append(providers[kind], providerRegistration{ProviderInstanceID: "openai-bootstrap", Kind: kind, EndpointRef: capability.EndpointBindingRef(envOr("OPENAI_ENDPOINT_REF", "openai-responses-default")), CredentialRef: capability.CredentialRef(envOr("OPENAI_CREDENTIAL_REF", "openai-advisory-v1")), AuthMode: capability.AuthKey, BillingMode: capability.BillingPerInput, Provider: openAIProvider})
providers[kind] = append(providers[kind], providerRegistration{ProviderInstanceID: "openai-bootstrap", Kind: kind, EndpointRef: capability.EndpointBindingRef(envOr("OPENAI_ENDPOINT_REF", "openai-responses-default")), CredentialRef: capability.CredentialRef(envOr("OPENAI_CREDENTIAL_REF", "openai-advisory-v1")), AuthMode: capability.AuthKey, BillingMode: capability.BillingPerInput, DataPolicy: capability.DataPolicyExternalMessageOnly, Provider: openAIProvider})
}
gateway := cognitiveGateway{resolver: resolver, providers: providers, now: time.Now, allowCanary: os.Getenv("COGNITIVE_ALLOW_CANARY") == "true", canaryMaxOutputBytes: 4096}
gateway := cognitiveGateway{resolver: resolver, providers: providers, builtinLocal: builtinLocal, externalRecovery: externalRecovery, now: time.Now, allowCanary: os.Getenv("COGNITIVE_ALLOW_CANARY") == "true", canaryMaxOutputBytes: 4096}
mux.Handle("/healthz", health.Handler{Runtime: "cognitive-worker", SemanticGeneration: 1, ArchitectureGeneration: 1})
mux.HandleFunc("POST /v1/proposals/deterministic", deterministicProposal)
mux.HandleFunc("POST /v1/advisory-policy", gateway.policyHandler)
@@ -99,7 +104,7 @@ func refreshProviderDiscoveries(resolver bindingResolver, service discoveryServi
Enabled: true,
OwnerPrincipalID: service.owner,
CapabilityID: creatorAdvisoryCapabilityID,
DataPolicy: capability.DataPolicyLocalOnly,
DataPolicy: registration.DataPolicy,
LimitPolicyRef: "creator-advisory-local-v1",
Health: probe.Health,
FailureClass: capability.CognitiveFailureType(probe.Failure),
+1 -1
View File
@@ -25,7 +25,7 @@ func TestChatPageServedWithoutInternalIDs(t *testing.T) {
t.Fatalf("expected /chat status 200, got %d", recorder.Code)
}
body := recorder.Body.String()
for _, required := range []string{"cognitive-status", "aria-live=\"polite\"", "cognitive-console-link", "href=\"/admin\"", "Проверь канал", "Изменить настройку", "ai-data-error", "Не вставляйте пароли", "версия 1.0.0", "обновлено 21.08.2026", "datetime=\"2026-08-21\""} {
for _, required := range []string{"cognitive-status", "aria-live=\"polite\"", "cognitive-console-link", "href=\"/admin\"", "Проверь канал", "Изменить настройку", "ai-data-error", "Не вставляйте пароли", "build-meta", "версия ", "обновлено ", "datetime=\""} {
if !strings.Contains(body, required) {
t.Fatalf("/chat is missing human status marker %q", required)
}
+29 -4
View File
@@ -1,6 +1,7 @@
package main
import (
"bytes"
"embed"
"encoding/json"
"fmt"
@@ -8,7 +9,9 @@ import (
"io/fs"
"net/http"
"net/url"
"os"
"strings"
"time"
"unicode/utf8"
"git.cin.su/m/Assistant/contracts/capability"
@@ -36,6 +39,12 @@ type gateway struct {
cognitive *url.URL
client *http.Client
static http.Handler
build buildMetadata
}
type buildMetadata struct {
revision string
updated time.Time
}
type routeTarget uint8
@@ -74,7 +83,20 @@ func newHandler(coreURL, continuityURL, commitURL string, client *http.Client, c
if client == nil {
client = http.DefaultClient
}
return securityHeaders(&gateway{core: core, continuity: continuity, commit: commit, cognitive: cognitiveRuntime, client: client, static: http.FileServer(http.FS(assets))}), nil
updated := time.Now().UTC()
if raw := strings.TrimSpace(os.Getenv("ASSISTANT_BUILD_TIME")); raw != "" {
if parsed, parseErr := time.Parse(time.RFC3339, raw); parseErr == nil {
updated = parsed.UTC()
}
}
revision := strings.TrimSpace(os.Getenv("ASSISTANT_BUILD_REVISION"))
if revision == "" {
revision = "development"
}
if len(revision) > 12 {
revision = revision[:12]
}
return securityHeaders(&gateway{core: core, continuity: continuity, commit: commit, cognitive: cognitiveRuntime, client: client, static: http.FileServer(http.FS(assets)), build: buildMetadata{revision: revision, updated: updated}}), nil
}
func parseRuntimeURL(rawURL, name string) (*url.URL, error) {
@@ -112,11 +134,11 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if r.URL.Path == "/" {
serveEmbeddedHTML(w, "chat.html")
g.serveEmbeddedHTML(w, "chat.html")
return
}
if r.URL.Path == "/admin" {
serveEmbeddedHTML(w, "index.html")
g.serveEmbeddedHTML(w, "index.html")
return
}
if r.URL.Path == "/chat" {
@@ -127,7 +149,7 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
g.static.ServeHTTP(w, r)
}
func serveEmbeddedHTML(w http.ResponseWriter, name string) {
func (g *gateway) serveEmbeddedHTML(w http.ResponseWriter, name string) {
body, err := staticFiles.ReadFile("static/" + name)
if err != nil {
http.Error(w, "page unavailable", http.StatusNotFound)
@@ -135,6 +157,9 @@ func serveEmbeddedHTML(w http.ResponseWriter, name string) {
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
body = bytes.ReplaceAll(body, []byte("__ASSISTANT_BUILD_REVISION__"), []byte(g.build.revision))
body = bytes.ReplaceAll(body, []byte("__ASSISTANT_BUILD_TIME__"), []byte(g.build.updated.Format("02.01.2006 15:04 UTC")))
body = bytes.ReplaceAll(body, []byte("__ASSISTANT_BUILD_DATETIME__"), []byte(g.build.updated.Format(time.RFC3339)))
_, _ = w.Write(body)
}
+2 -2
View File
@@ -29,8 +29,8 @@
<button id="cognitive-status" class="cognitive-status" data-state="checking" type="button" role="status" aria-live="polite" title="Проверить канал ещё раз">проверяю канал мышления</button>
<a id="cognitive-console-link" class="cognitive-console-link" href="/admin" hidden>настроить в консоли</a>
<div class="build-meta" aria-label="Версия и время обновления">
<span>версия 1.0.0</span>
<time datetime="2026-08-21">обновлено 21.08.2026</time>
<span>версия __ASSISTANT_BUILD_REVISION__</span>
<time datetime="__ASSISTANT_BUILD_DATETIME__">обновлено __ASSISTANT_BUILD_TIME__</time>
</div>
<a href="/admin" class="operator-link">Консоль</a>
<button id="logout" type="button" aria-label="Выйти">↗</button>
+5 -2
View File
@@ -141,7 +141,7 @@ services:
- --port
- "8086"
- --model
- /models/qwen2.5-0.5b-instruct-q4_k_m.gguf
- /models/Qwen3-8B-Q4_K_M.gguf
- --alias
- domovoy-lifeboat
- --jinja
@@ -150,9 +150,11 @@ services:
- --n-gpu-layers
- "0"
- --threads
- "4"
- "32"
- --parallel
- "1"
- --reasoning
- "off"
volumes:
- llm_local_models:/models:ro
healthcheck:
@@ -170,6 +172,7 @@ services:
CONTINUITY_RUNTIME_URL: http://continuity-runtime:8081
COMMIT_RUNTIME_URL: http://commit-runtime:8082
COGNITIVE_RUNTIME_URL: http://cognitive-worker:8083
ASSISTANT_BUILD_REVISION: ${ASSISTANT_SOURCE_REVISION:-unknown}
ports: ["${CREATOR_WEB_PORT:-18080}:8084"]
depends_on: [core-runtime, continuity-runtime, commit-runtime, cognitive-worker]
healthcheck:
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000082-chat-ai-data-safety-guard.json",
"task_id": "DEV-000082",
"task_file": "dev/tasks/DEV-000089-domovoy-cognitive-upgrade-integration.json",
"task_id": "DEV-000089",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000085",
"task_revision": 1,
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "cdc8eea1a747657ce0534b99ff35933ab3385877",
"risk_tier": "C",
"intent": "Keep Domovoy useful when external or active cognitive bindings are unavailable by invoking the built-in local lifeboat through the Cognitive Gateway and presenting an actionable recovery focus when all cognition fails.",
"requirement_ids": ["Built-in local cognition remains available", "Provider loss does not collapse conversation", "Recovery state is explained and actionable"],
"allowed_change_scope": {"modules": ["cognitive-worker"], "paths": ["cmd/cognitive-worker/gateway.go", "cmd/cognitive-worker/interpretation.go", "cmd/cognitive-worker/main.go", "cmd/cognitive-worker/gateway_registry_test.go", "cmd/cognitive-worker/interpretation_test.go", "dev/tasks/DEV-000085-builtin-cognitive-recovery.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_provider_adapters": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing Cognitive Gateway and local provider adapter contracts"], "may_change": []},
"semantic_owner": "capability",
"canonical_state": [],
"state_transitions": ["No active binding -> built-in local invocation", "Built-in invocation failure -> recovery-focused degraded response"],
"side_effects_and_privileges": ["No new privilege", "Built-in path is local-only and cannot egress externally"],
"persistence_and_migrations": ["No persistence or migration"],
"failure_retry_restart": ["Provider failure remains observable; next request retries resolution and built-in local path"],
"assumptions": ["The configured local lifeboat endpoint is part of Domovoy and may be used without external egress acknowledgement"],
"acceptance_scenarios": ["Inactive registry with healthy local lifeboat invokes the local adapter", "Interpretation also uses the built-in local path", "All cognition failure returns an actionable recovery response"],
"required_tests": ["go test ./cmd/cognitive-worker", "go test ./...", "go vet ./...", "git diff --check"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000086",
"task_revision": 1,
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "cdc8eea1a747657ce0534b99ff35933ab3385877",
"risk_tier": "C",
"intent": "Replace the unusable tiny lifeboat model with a capable, locally hosted Qwen3 8B quantized baseline while keeping Domovoy runtime on docker.cin.su.",
"requirement_ids": ["Built-in local cognition is useful for ordinary dialogue", "Local model remains available without external egress", "Model choice is explicit and replaceable"],
"allowed_change_scope": {"modules": ["deployment", "cognitive-worker"], "paths": ["deploy/docker-compose.prod.yml", "dev/tasks/DEV-000086-local-model-baseline.json", "dev/tasks/DEV-000085-builtin-cognitive-recovery.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_provider_adapters": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing llama.cpp OpenAI-compatible local endpoint", "existing Cognitive Gateway"], "may_change": []},
"semantic_owner": "capability",
"canonical_state": [],
"state_transitions": ["Configured model file -> loaded local baseline", "Local model replacement -> restart and health verification"],
"side_effects_and_privileges": ["No new privilege", "Model is local-only and mounted read-only into the runtime"],
"persistence_and_migrations": ["No persistence or migration"],
"failure_retry_restart": ["Missing or invalid model keeps the container unhealthy and does not silently fall back to the tiny model"],
"assumptions": ["Qwen3-8B-Q4_K_M.gguf is present in the protected Docker model volume on docker.cin.su before rollout"],
"acceptance_scenarios": ["The configured lifeboat starts with the named Qwen3 8B model", "The OpenAI-compatible health endpoint is healthy", "No other host is used for Domovoy runtime"],
"required_tests": ["go test ./cmd/cognitive-worker", "go test ./...", "go vet ./...", "git diff --check", "production health and model endpoint verification"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT", "REQUIRES_MODEL_ARTIFACT"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000087",
"task_revision": 1,
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "cdc8eea1a747657ce0534b99ff35933ab3385877",
"risk_tier": "D",
"intent": "When the selected cognitive route is unavailable, keep Domovoy conversational, diagnose the failure, use the built-in local model, and probe explicitly configured external GPU/HTTP providers through the Cognitive Gateway without exposing credentials or bypassing egress policy.",
"requirement_ids": ["Provider loss becomes a recovery objective", "Meaning and task objective guide provider choice", "External egress requires explicit acknowledgement", "Credentials remain opaque secret references"],
"allowed_change_scope": {"modules": ["cognitive-worker"], "paths": ["cmd/cognitive-worker/gateway.go", "cmd/cognitive-worker/interpretation.go", "cmd/cognitive-worker/main.go", "cmd/cognitive-worker/recovery.go", "cmd/cognitive-worker/gateway_registry_test.go", "cmd/cognitive-worker/interpretation_test.go", "cmd/cognitive-worker/recovery_test.go", "dev/tasks/DEV-000087-cognitive-recovery-orchestrator.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_provider_adapters": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing typed Cognitive Gateway", "existing local and Ollama adapters", "existing discovery and binding resolver"], "may_change": []},
"semantic_owner": "capability",
"canonical_state": [],
"state_transitions": ["Selected provider failure -> local built-in attempt", "Local failure -> recovery diagnosis and configured-provider probe", "External probe -> registry discovery only; invocation requires policy acknowledgement"],
"side_effects_and_privileges": ["No new privilege", "No arbitrary network scanning", "No credential material in logs or messages"],
"persistence_and_migrations": ["Existing discovery registration only; no new persistence"],
"failure_retry_restart": ["Bounded sequential probes per request", "No unmanaged background retry loop", "Each new request re-resolves and re-probes"],
"assumptions": ["External GPU/HTTP endpoints are explicitly configured by deployment operators", "Core Runtime remains the owner of activation and selection"],
"acceptance_scenarios": ["Inactive registry with healthy local model still produces an intelligent local response", "Interpretation uses the same local recovery path", "External endpoints are discovered but never invoked without egress acknowledgement", "All failures yield actionable recovery text instead of a generic unavailable error"],
"required_tests": ["go test ./cmd/cognitive-worker", "go test ./...", "go vet ./...", "git diff --check", "production recovery request and provider health verification"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT", "BLOCKED_BY_MISSING_CAPABILITY"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000088",
"task_revision": 1,
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "cdc8eea1a747657ce0534b99ff35933ab3385877",
"risk_tier": "B",
"intent": "Expose self-describing build revision and runtime update time in the Domovoy chat without requiring a human status query.",
"requirement_ids": ["Human can identify the running build", "Displayed update time reflects the running container", "No internal identifiers are exposed in the chat UI"],
"allowed_change_scope": {"modules": ["creator-web", "deployment"], "paths": ["cmd/creator-web/server.go", "cmd/creator-web/chat_test.go", "cmd/creator-web/static/chat.html", "deploy/docker-compose.prod.yml", "dev/tasks/DEV-000088-runtime-build-status.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_provider_adapters": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing source revision build argument"], "may_change": []},
"semantic_owner": "creator-web",
"canonical_state": [],
"state_transitions": ["Container startup -> build metadata visible in chat"],
"side_effects_and_privileges": ["No new privilege", "No user or provider secrets displayed"],
"persistence_and_migrations": ["No persistence or migration"],
"failure_retry_restart": ["Missing revision is displayed as development/unknown; startup time remains observable"],
"assumptions": ["The production compose runner supplies ASSISTANT_SOURCE_REVISION for the running revision"],
"acceptance_scenarios": ["Chat shows revision and update time without a manual question", "HTML contains no internal provider or session identifiers", "Tests remain deterministic outside production"],
"required_tests": ["go test ./cmd/creator-web", "go test ./...", "go vet ./...", "git diff --check", "production page verification"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000089",
"task_revision": 1,
"architecture_revision": "DEV-000073-baseline",
"base_repo_revision": "9f717d720e217afd4cc87a4fbf851050d466d8cd",
"risk_tier": "E",
"intent": "Integrate the Domovoy local-model baseline, cognitive recovery orchestrator, and self-describing runtime status into one reviewed production change.",
"requirement_ids": ["Built-in cognition remains useful", "Provider loss triggers recovery", "Production runtime identifies its revision and update time"],
"allowed_change_scope": {"modules": ["cognitive-worker", "creator-web", "deployment", "development-governance"], "paths": ["cmd/cognitive-worker/discovery.go", "cmd/cognitive-worker/gateway.go", "cmd/cognitive-worker/gateway_registry_test.go", "cmd/cognitive-worker/interpretation.go", "cmd/cognitive-worker/main.go", "cmd/creator-web/chat_test.go", "cmd/creator-web/server.go", "cmd/creator-web/static/chat.html", "deploy/docker-compose.prod.yml", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000085-builtin-cognitive-recovery.json", "dev/tasks/DEV-000086-local-model-baseline.json", "dev/tasks/DEV-000087-cognitive-recovery-orchestrator.json", "dev/tasks/DEV-000088-runtime-build-status.json", "dev/tasks/DEV-000089-domovoy-cognitive-upgrade-integration.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing cognitive gateway", "existing creator-web runtime", "existing production compose runner"], "may_change": []},
"semantic_owner": "capability",
"canonical_state": [],
"state_transitions": ["Old active task -> integrated reviewed task", "Old local baseline -> Qwen3-8B baseline", "Unavailable active binding -> built-in and configured external recovery"],
"side_effects_and_privileges": ["No new privilege", "External invocation remains acknowledgement-gated", "No secrets in source or UI"],
"persistence_and_migrations": ["No persistence or migration"],
"failure_retry_restart": ["Existing auto-update runner deploys the merged main revision", "Missing model artifact leaves the lifeboat unhealthy rather than silently selecting the tiny model"],
"assumptions": ["Qwen3-8B-Q4_K_M.gguf is present in the protected Docker model volume on docker.cin.su"],
"acceptance_scenarios": ["CI accepts exact task scope", "Production chat shows running revision and update time", "Domovoy can answer through built-in local cognition when registry activation is unavailable"],
"required_tests": ["go test ./...", "go vet ./...", "devctl preflight", "devctl check-diff", "git diff --check", "production health and revision verification"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT", "REQUIRES_MODEL_ARTIFACT"]
}