Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40b7bd1c14 | ||
|
|
1c334fcce6 | ||
|
|
fd59b93463 | ||
|
|
ce666bbc43 | ||
|
|
64870798b9 |
@@ -2,16 +2,45 @@
|
||||
|
||||
You are implementing this repository under a frozen architecture generation. **Capability to edit files is not authority to change architecture.**
|
||||
|
||||
## Durable project principles
|
||||
|
||||
- Domovoy is a long-lived personal assistant, not a one-off chatbot or coding helper.
|
||||
- Current specialization is temporary; architecture is implementation-specific and revisable by approved architecture decisions.
|
||||
- Codex is an implementation capability, not architectural owner by default.
|
||||
- Model/provider is implementation infrastructure, not Domovoy identity.
|
||||
- Human reality claims are only claims until supported.
|
||||
- Human intent/preferences are strong evidence, even when not external facts.
|
||||
- UNKNOWN is a valid state and must remain explicit.
|
||||
- Reversible/low-risk safety is required before irreversible action.
|
||||
- Deterministic reflex is optimization, not final semantic truth.
|
||||
- Capability, transport, and implementation are separate concerns.
|
||||
- Capability usage requires declared evidence and observed capability gap handling.
|
||||
- LLM cost/brand/availability is evidence, not a blanket truth rule.
|
||||
- Experience is not learning by itself; only measured behavior change becomes evidence.
|
||||
- Normal user interaction must stay zero-ops.
|
||||
- Live configuration and canonical state should be portable and bootstrap-only environment should stay minimal.
|
||||
- Production continuity must include recovery and provenance across model/binding/config/loader/operator changes.
|
||||
- Do not overbuild future systems for current milestone.
|
||||
|
||||
## Mandatory startup sequence
|
||||
|
||||
Before any non-trivial change:
|
||||
|
||||
1. Read `architecture/ARCHITECTURE_CONSTITUTION.md`.
|
||||
2. Read `architecture/CORE_SEMANTIC_MODEL_V1.md`.
|
||||
3. Read `architecture/architecture-registry.yaml` and `architecture/architecture-registry.json`.
|
||||
4. Read `development/CODEX_DEVELOPMENT_CONTRACT.md`.
|
||||
5. Read the active file under `dev/tasks/`.
|
||||
6. Run `go run ./cmd/devctl preflight -task <task-file>` for Tier B+ tasks when available.
|
||||
1. Inspect git branch, status, and recent ancestry (at least `git status`, `git rev-parse`, `git log -1`).
|
||||
2. Read `AGENTS.md`.
|
||||
3. Read `architecture/ARCHITECTURE_CONSTITUTION.md`.
|
||||
4. Read `architecture/CORE_SEMANTIC_MODEL_V1.md`.
|
||||
5. Read `architecture/architecture-registry.yaml` and `architecture/architecture-registry.json`.
|
||||
6. Read `development/CODEX_DEVELOPMENT_CONTRACT.md`.
|
||||
7. Read `dev/ACTIVE_TASK.json`.
|
||||
8. Read the current task packet under `dev/tasks/` referenced by `ACTIVE_TASK`.
|
||||
9. Read relevant accepted ADRs from `architecture/adrs`.
|
||||
10. Inspect `docs/PROJECT_STATUS.md` and current evidence artifacts for checkpoint continuity.
|
||||
11. Inspect current runtime evidence/check commands before implementation.
|
||||
12. Run `go run ./cmd/devctl preflight -task <task-file>` for Tier B+ tasks.
|
||||
13. Then proceed with implementation.
|
||||
|
||||
For a fresh session, this sequence is the required cold-start reconstruction path and must be documented in the task checkpoint.
|
||||
|
||||
If documents conflict, authority order is:
|
||||
|
||||
@@ -72,3 +101,32 @@ State:
|
||||
- assumptions;
|
||||
- findings not changed;
|
||||
- gaps or limitations.
|
||||
|
||||
## Session reconstruction checkpoint (Durable memory)
|
||||
|
||||
Before leaving a DEV, include machine-readable checkpoint facts in:
|
||||
|
||||
- `dev/ACTIVE_TASK.json`
|
||||
- active task packet under `dev/tasks/`
|
||||
- `docs/PROJECT_STATUS.md`
|
||||
- `docs/COLD_START_AND_DURABLE_CHECKPOINT.md`
|
||||
|
||||
The checkpoint must explicitly carry:
|
||||
|
||||
- `STATUS`
|
||||
- `CURRENT_PHASE`
|
||||
- `COMPLETED`
|
||||
- `IN_PROGRESS`
|
||||
- `NOT_STARTED`
|
||||
- `BLOCKERS`
|
||||
- `IMPORTANT_FINDINGS`
|
||||
- `EVIDENCE_OBTAINED`
|
||||
- `LAST_VERIFIED_COMMIT`
|
||||
- `NEXT_ACTION`
|
||||
|
||||
No product behavior or canonical semantic changes are required for this milestone.
|
||||
|
||||
The fresh-session recovery procedure and evidence expectations are documented in:
|
||||
`docs/COLD_START_AND_DURABLE_CHECKPOINT.md`.
|
||||
|
||||
Execution-window exhaustion is continuation, not failure; do not mark a task done only because a session ends.
|
||||
|
||||
@@ -43,15 +43,25 @@ func (r *InteractionRepository) AppendUtterance(ctx context.Context, c interacti
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *InteractionRepository) SaveSemanticResult(ctx context.Context, interpretation interaction.UtteranceInterpretation, projects []interaction.ProjectReference, relations []interaction.InteractionRelation) error {
|
||||
func (r *InteractionRepository) SaveSemanticResult(ctx context.Context, interpretation interaction.UtteranceInterpretation, candidates []interaction.SemanticCandidate, projects []interaction.ProjectReference, relations []interaction.InteractionRelation) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
if _, err = tx.Exec(ctx, `INSERT INTO interaction.utterance_interpretations(interpretation_id,utterance_id,speech_act,confidence_millis,source,assertion_verification,requested_outcome,clarification,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)`, interpretation.ID, interpretation.UtteranceID, interpretation.SpeechAct, interpretation.ConfidenceMillis, interpretation.Source, interpretation.AssertionVerification, interpretation.RequestedOutcome, interpretation.Clarification, interpretation.RecordedAtUnixMillis); err != nil {
|
||||
if _, err = tx.Exec(ctx, `INSERT INTO interaction.utterance_interpretations(interpretation_id,utterance_id,speech_act,secondary_speech_act,confidence_millis,source,assertion_verification,requested_outcome,clarification,needs_cognition,applicability,selection_reason,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)`, interpretation.ID, interpretation.UtteranceID, interpretation.SpeechAct, interpretation.SecondarySpeechAct, interpretation.ConfidenceMillis, interpretation.Source, interpretation.AssertionVerification, interpretation.RequestedOutcome, interpretation.Clarification, interpretation.NeedsCognition, interpretation.Applicability, interpretation.SelectionReason, interpretation.RecordedAtUnixMillis); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, candidate := range candidates {
|
||||
if _, err = tx.Exec(ctx, `INSERT INTO interaction.semantic_candidates(candidate_id,utterance_id,source,selection_state,primary_speech_act,secondary_speech_act,confidence_millis,applicability,requested_outcome,clarification,needs_cognition,selection_reason,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) ON CONFLICT(candidate_id) DO UPDATE SET selection_state=EXCLUDED.selection_state,primary_speech_act=EXCLUDED.primary_speech_act,secondary_speech_act=EXCLUDED.secondary_speech_act,confidence_millis=EXCLUDED.confidence_millis,applicability=EXCLUDED.applicability,requested_outcome=EXCLUDED.requested_outcome,clarification=EXCLUDED.clarification,needs_cognition=EXCLUDED.needs_cognition,selection_reason=EXCLUDED.selection_reason,recorded_at_unix_millis=EXCLUDED.recorded_at_unix_millis`, candidate.ID, candidate.UtteranceID, candidate.Source, candidate.SelectionState, candidate.PrimarySpeechAct, candidate.SecondarySpeechAct, candidate.ConfidenceMillis, candidate.Applicability, candidate.RequestedOutcome, candidate.Clarification, candidate.NeedsCognition, candidate.SelectionReason, candidate.RecordedAtUnixMillis); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, hint := range candidate.CapabilityHints {
|
||||
if _, err = tx.Exec(ctx, `INSERT INTO interaction.semantic_candidate_capability_hints(candidate_id,capability_kind) VALUES($1,$2) ON CONFLICT DO NOTHING`, candidate.ID, hint); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, project := range projects {
|
||||
if _, err = tx.Exec(ctx, `INSERT INTO interaction.project_references(project_reference_id,utterance_id,project_id,project_display_name,role,provenance,confidence_millis,recorded_at_unix_millis) VALUES($1,$2,$3,$4,$5,$6,$7,$8)`, project.ID, project.UtteranceID, project.ProjectID, project.ProjectDisplayName, project.Role, project.Provenance, project.ConfidenceMillis, project.RecordedAtUnixMillis); err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||
)
|
||||
|
||||
func (g cognitiveGateway) interpretationHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var request cognitive.GatewayInterpretationRequest
|
||||
if !decodeJSON(w, r, 80<<10, &request) {
|
||||
return
|
||||
}
|
||||
if request.Version != cognitive.CreatorAdvisoryV1 || request.RequestID == "" || request.ActorID == "" || request.PrincipalID == "" || request.ScopeID == "" || strings.TrimSpace(request.Message) == "" || request.MaxOutputBytes < 1 {
|
||||
http.Error(w, "invalid interpretation request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
maxOutputBytes := request.MaxOutputBytes
|
||||
if g.allowCanary && g.canaryMaxOutputBytes > 0 && maxOutputBytes > g.canaryMaxOutputBytes {
|
||||
maxOutputBytes = g.canaryMaxOutputBytes
|
||||
}
|
||||
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),
|
||||
}
|
||||
selected, err := g.resolver.Resolve(r.Context(), requirement)
|
||||
if err != nil {
|
||||
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable})
|
||||
return
|
||||
}
|
||||
provider := g.provider(selected)
|
||||
if provider == nil {
|
||||
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishProviderUnavailable})
|
||||
return
|
||||
}
|
||||
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: selected.Binding.ID,
|
||||
ProviderBindingRevision: selected.Binding.Revision,
|
||||
ProviderInstanceID: selected.Provider.ID,
|
||||
ModelDescriptorID: selected.Model.ID,
|
||||
ProviderModelID: selected.Model.ProviderModelID,
|
||||
}
|
||||
started := g.now()
|
||||
response, invokeErr := provider.Advise(r.Context(), invocation)
|
||||
if invokeErr != nil {
|
||||
response = cognitive.AdvisoryResponse{Version: request.Version, RequestID: request.RequestID, ProviderBindingID: selected.Binding.ID, ProviderBindingRevision: selected.Binding.Revision, FinishReason: cognitive.FinishInvalidProviderResponse}
|
||||
}
|
||||
g.observe(r.Context(), selected, response, started)
|
||||
if response.FinishReason != cognitive.FinishCompleted {
|
||||
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: response.FinishReason, Usage: response.Usage})
|
||||
return
|
||||
}
|
||||
candidate, ok := parseInterpretationCandidate(response.AdvisoryText)
|
||||
if !ok {
|
||||
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishInvalidStructuredOutput, Usage: response.Usage})
|
||||
return
|
||||
}
|
||||
writeInterpretationResponse(w, cognitive.GatewayInterpretationResponse{Version: request.Version, RequestID: request.RequestID, Candidate: candidate, FinishReason: cognitive.FinishCompleted, Usage: response.Usage})
|
||||
}
|
||||
|
||||
func interpretationPrompt(request cognitive.GatewayInterpretationRequest) string {
|
||||
projects := "none"
|
||||
if len(request.ProjectNames) > 0 {
|
||||
projects = strings.Join(request.ProjectNames, ", ")
|
||||
}
|
||||
return fmt.Sprintf(`Return only strict JSON with keys primary_speech_act, secondary_speech_act, confidence_millis, applicability, requested_outcome, needs_cognition, clarification, selection_reason, capability_hints.
|
||||
Allowed speech acts: DIRECTIVE, ASSERTION, DECISION, PREFERENCE, QUESTION, HYPOTHESIS, CORRECTION, REQUEST_TO_INVESTIGATE, CONFIRMATION, REJECTION, CANCELLATION, CONVERSATIONAL, UNKNOWN.
|
||||
Allowed applicability: APPLICABLE, LOW_CONFIDENCE, MIXED_INTENT, NEEDS_BROADER_INTERPRETATION, CLARIFICATION_REQUIRED.
|
||||
Allowed clarification: NOT_REQUIRED or REQUIRED.
|
||||
Capability hints may only contain known capability kinds like COGNITIVE_REASONING, MODIFY_CODE, RUN_TESTS, SEND_EMAIL, SCHEDULE_REMINDER, VERIFY_FACT, INSPECT_PROJECT, READ_DATABASE, QUERY_LOGS.
|
||||
Interpret the human utterance semantically. Do not claim action execution. Do not treat punctuation alone as decisive. If uncertain, set applicability accordingly.
|
||||
Message: %q
|
||||
Deterministic primary: %s
|
||||
Deterministic secondary: %s
|
||||
Deterministic confidence: %d
|
||||
Deterministic applicability: %s
|
||||
Deterministic requested outcome: %q
|
||||
Relevant project names: %s`, request.Message, request.DeterministicSpeechAct, request.DeterministicSecondary, request.DeterministicConfidence, request.DeterministicApplicability, request.RequestedOutcome, projects)
|
||||
}
|
||||
|
||||
func parseInterpretationCandidate(text string) (cognitive.GatewayInterpretationCandidate, bool) {
|
||||
start := strings.Index(text, "{")
|
||||
end := strings.LastIndex(text, "}")
|
||||
if start < 0 || end <= start {
|
||||
return cognitive.GatewayInterpretationCandidate{}, false
|
||||
}
|
||||
var candidate cognitive.GatewayInterpretationCandidate
|
||||
if err := json.Unmarshal([]byte(text[start:end+1]), &candidate); err != nil {
|
||||
return cognitive.GatewayInterpretationCandidate{}, false
|
||||
}
|
||||
if !validSpeechAct(candidate.PrimarySpeechAct) || candidate.ConfidenceMillis < 0 || candidate.ConfidenceMillis > 1000 {
|
||||
return cognitive.GatewayInterpretationCandidate{}, false
|
||||
}
|
||||
if candidate.SecondarySpeechAct != "" && !validSpeechAct(candidate.SecondarySpeechAct) {
|
||||
return cognitive.GatewayInterpretationCandidate{}, false
|
||||
}
|
||||
switch candidate.Applicability {
|
||||
case cognitive.InterpretationApplicable, cognitive.InterpretationLowConfidence, cognitive.InterpretationMixedIntent, cognitive.InterpretationNeedsBroader, cognitive.InterpretationClarificationRequired:
|
||||
default:
|
||||
return cognitive.GatewayInterpretationCandidate{}, false
|
||||
}
|
||||
if candidate.Clarification != "NOT_REQUIRED" && candidate.Clarification != "REQUIRED" {
|
||||
return cognitive.GatewayInterpretationCandidate{}, false
|
||||
}
|
||||
return candidate, true
|
||||
}
|
||||
|
||||
func validSpeechAct(value string) bool {
|
||||
switch value {
|
||||
case "DIRECTIVE", "ASSERTION", "DECISION", "PREFERENCE", "QUESTION", "HYPOTHESIS", "CORRECTION", "REQUEST_TO_INVESTIGATE", "CONFIRMATION", "REJECTION", "CANCELLATION", "CONVERSATIONAL", "UNKNOWN":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func writeInterpretationResponse(w http.ResponseWriter, response cognitive.GatewayInterpretationResponse) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/adapters/models"
|
||||
"git.cin.su/m/Assistant/contracts/authority"
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||
"git.cin.su/m/Assistant/contracts/identity"
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
"git.cin.su/m/Assistant/contracts/situation"
|
||||
interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain"
|
||||
)
|
||||
|
||||
func TestLiveInterpretationTrace(t *testing.T) {
|
||||
if os.Getenv("ASSISTANT_LIVE_SEMANTIC_TRACE") != "1" {
|
||||
t.Skip("set ASSISTANT_LIVE_SEMANTIC_TRACE=1 for live semantic/gateway/raw trace")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rawBase := envOr("LOCAL_CPU_PROVIDER_URL", "http://docker.cin.su:18082/v1")
|
||||
rawModelID, err := firstRawModel(ctx, rawBase)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cpuAdapter, err := models.NewOpenAICompatibleLocalAdapter(rawBase, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
discovered, probe := cpuAdapter.Discover(ctx)
|
||||
if probe.Health != capability.HealthAvailable || len(discovered) == 0 {
|
||||
t.Fatalf("cpu discovery unavailable: probe=%+v models=%+v", probe, discovered)
|
||||
}
|
||||
selected := capability.ResolvedCognitiveBinding{
|
||||
Binding: capability.CognitiveBinding{
|
||||
ID: "trace-binding",
|
||||
Revision: 1,
|
||||
CapabilityID: "creator-advisory",
|
||||
OwnerPrincipalID: "domovoy-creator-principal",
|
||||
DataPolicy: capability.DataPolicyLocalOnly,
|
||||
},
|
||||
Provider: capability.ProviderInstance{
|
||||
ID: "local-cpu-primary",
|
||||
Revision: 1,
|
||||
Kind: "openai-compatible-chat-v1",
|
||||
EndpointRef: "local-cpu-primary-v1",
|
||||
},
|
||||
Model: capability.ModelDescriptor{
|
||||
ID: capability.ModelDescriptorID("trace-model"),
|
||||
Revision: 1,
|
||||
ProviderModelID: discovered[0].ProviderModelID,
|
||||
},
|
||||
}
|
||||
gateway := cognitiveGateway{
|
||||
resolver: traceResolver{selected: selected},
|
||||
providers: map[capability.ProviderKind][]providerRegistration{
|
||||
"openai-compatible-chat-v1": {{
|
||||
ProviderInstanceID: "local-cpu-primary",
|
||||
Kind: "openai-compatible-chat-v1",
|
||||
EndpointRef: "local-cpu-primary-v1",
|
||||
Provider: cpuAdapter,
|
||||
}},
|
||||
},
|
||||
now: time.Now,
|
||||
allowCanary: true,
|
||||
}
|
||||
for _, message := range []string{"ты кто", "можешь расказать об утре"} {
|
||||
conversation := interaction.Conversation{ID: "trace-conversation", ActorID: "trace-actor", PrincipalID: "domovoy-creator-principal", Channel: interaction.ChannelWebChat, State: interaction.ConversationOpen}
|
||||
utterance, _, _, err := interactiondomain.NewHumanUtterance(conversation, interaction.ChatMessageRequest{Message: message}, time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
semantic, err := interactiondomain.InterpretUtterance(utterance, []situation.ProjectDescriptor{{ID: "assistant", DisplayName: "Assistant"}}, nil, time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rawResponse, err := rawCompletion(ctx, rawBase, rawModelID, message)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
request := cognitive.GatewayInterpretationRequest{
|
||||
Version: cognitive.CreatorAdvisoryV1,
|
||||
RequestID: "trace-request",
|
||||
ActorID: identity.ActorID("trace-actor"),
|
||||
PrincipalID: identity.PrincipalID("domovoy-creator-principal"),
|
||||
ScopeID: authority.ScopeID("domovoy-global-scope"),
|
||||
Message: message,
|
||||
Locale: "ru",
|
||||
MaxOutputBytes: 4096,
|
||||
DataPolicy: capability.DataPolicyLocalOnly,
|
||||
DeterministicSpeechAct: string(semantic.Interpretation.SpeechAct),
|
||||
DeterministicSecondary: string(semantic.Interpretation.SecondarySpeechAct),
|
||||
DeterministicConfidence: semantic.Interpretation.ConfidenceMillis,
|
||||
DeterministicApplicability: cognitive.InterpretationApplicability(semantic.Interpretation.Applicability),
|
||||
RequestedOutcome: semantic.Interpretation.RequestedOutcome,
|
||||
}
|
||||
payload, _ := json.Marshal(request)
|
||||
recorder := httptest.NewRecorder()
|
||||
httpRequest := httptest.NewRequest(http.MethodPost, "/v1/interpretations", bytes.NewReader(payload))
|
||||
gateway.interpretationHandler(recorder, httpRequest.WithContext(ctx))
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("message=%q gateway status=%d body=%s", message, recorder.Code, recorder.Body.String())
|
||||
}
|
||||
var gatewayResponse cognitive.GatewayInterpretationResponse
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &gatewayResponse); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if gatewayResponse.FinishReason != cognitive.FinishCompleted {
|
||||
t.Fatalf("message=%q gateway finish=%s body=%s", message, gatewayResponse.FinishReason, recorder.Body.String())
|
||||
}
|
||||
if rawResponse == "" {
|
||||
t.Fatalf("message=%q raw response is empty", message)
|
||||
}
|
||||
if message == "ты кто" {
|
||||
if semantic.Interpretation.SpeechAct != interaction.SpeechActQuestion || !semantic.NeedsCognition {
|
||||
t.Fatalf("semantic path still fails punctuation-free identity question: %+v", semantic.Interpretation)
|
||||
}
|
||||
if gatewayResponse.Candidate.PrimarySpeechAct != "QUESTION" {
|
||||
t.Fatalf("gateway did not preserve question semantics: %+v", gatewayResponse.Candidate)
|
||||
}
|
||||
}
|
||||
t.Logf("TRACE message=%q deterministic=%s/%s needs_cognition=%t raw=%q gateway_candidate=%+v", message, semantic.Interpretation.SpeechAct, semantic.Interpretation.Applicability, semantic.NeedsCognition, truncate(rawResponse, 180), gatewayResponse.Candidate)
|
||||
}
|
||||
}
|
||||
|
||||
type traceResolver struct {
|
||||
selected capability.ResolvedCognitiveBinding
|
||||
}
|
||||
|
||||
func (r traceResolver) Resolve(context.Context, capability.CognitiveRequirement) (capability.ResolvedCognitiveBinding, error) {
|
||||
return r.selected, nil
|
||||
}
|
||||
|
||||
func (r traceResolver) Observe(context.Context, capability.CognitiveUsageObservation) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r traceResolver) RegisterDiscovery(context.Context, capability.CognitiveDiscoverySnapshot) (capability.CognitiveDiscoveryRegistration, error) {
|
||||
return capability.CognitiveDiscoveryRegistration{}, nil
|
||||
}
|
||||
|
||||
func firstRawModel(ctx context.Context, base string) (string, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, base+"/models", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
var payload struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.NewDecoder(io.LimitReader(response.Body, 64<<10)).Decode(&payload); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(payload.Data) == 0 || payload.Data[0].ID == "" {
|
||||
return "", io.EOF
|
||||
}
|
||||
return payload.Data[0].ID, nil
|
||||
}
|
||||
|
||||
func rawCompletion(ctx context.Context, base, model, message string) (string, error) {
|
||||
payload, err := json.Marshal(map[string]any{
|
||||
"model": model,
|
||||
"max_tokens": 128,
|
||||
"messages": []map[string]string{
|
||||
{"role": "user", "content": message},
|
||||
},
|
||||
"stream": false,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, base+"/chat/completions", bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
var result struct {
|
||||
Choices []struct {
|
||||
Message struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"message"`
|
||||
} `json:"choices"`
|
||||
}
|
||||
if err := json.NewDecoder(io.LimitReader(response.Body, 1<<20)).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(result.Choices) == 0 {
|
||||
return "", io.EOF
|
||||
}
|
||||
return result.Choices[0].Message.Content, nil
|
||||
}
|
||||
|
||||
func truncate(value string, max int) string {
|
||||
if len(value) <= max {
|
||||
return value
|
||||
}
|
||||
return value[:max] + "..."
|
||||
}
|
||||
@@ -63,6 +63,7 @@ func main() {
|
||||
mux.HandleFunc("POST /v1/proposals/deterministic", deterministicProposal)
|
||||
mux.HandleFunc("POST /v1/advisory-policy", gateway.policyHandler)
|
||||
mux.HandleFunc("POST /v1/advisories", gateway.advisoryHandler)
|
||||
mux.HandleFunc("POST /v1/interpretations", gateway.interpretationHandler)
|
||||
discovery := discoveryService{resolver: resolver, providers: discoveries, owner: identity.PrincipalID(envOr("COGNITIVE_OWNER_PRINCIPAL_ID", "domovoy-creator-principal")), now: time.Now}
|
||||
mux.HandleFunc("POST /v1/provider-discovery/{provider}", discovery.handler)
|
||||
log.Printf("cognitive-worker listening on %s", addr)
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/authority"
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||
"git.cin.su/m/Assistant/contracts/identity"
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain"
|
||||
"git.cin.su/m/Assistant/platform/ids"
|
||||
)
|
||||
|
||||
type cognitiveInterpretationClient struct {
|
||||
baseURL *url.URL
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func newCognitiveInterpretationClient(rawURL string, client *http.Client) (cognitiveInterpretationClient, error) {
|
||||
base, err := url.Parse(rawURL)
|
||||
if err != nil || base.Scheme == "" || base.Host == "" {
|
||||
return cognitiveInterpretationClient{}, fmt.Errorf("invalid Cognitive Runtime URL")
|
||||
}
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
return cognitiveInterpretationClient{baseURL: base, client: client}, nil
|
||||
}
|
||||
|
||||
func (c cognitiveInterpretationClient) Interpret(ctx context.Context, request cognitive.GatewayInterpretationRequest) (cognitive.GatewayInterpretationResponse, error) {
|
||||
target := *c.baseURL
|
||||
target.Path = "/v1/interpretations"
|
||||
payload, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return cognitive.GatewayInterpretationResponse{}, err
|
||||
}
|
||||
upstream, err := http.NewRequestWithContext(ctx, http.MethodPost, target.String(), bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return cognitive.GatewayInterpretationResponse{}, err
|
||||
}
|
||||
upstream.Header.Set("Content-Type", "application/json")
|
||||
response, err := c.client.Do(upstream)
|
||||
if err != nil {
|
||||
return cognitive.GatewayInterpretationResponse{}, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return cognitive.GatewayInterpretationResponse{}, fmt.Errorf("cognitive interpretation status %d", response.StatusCode)
|
||||
}
|
||||
var result cognitive.GatewayInterpretationResponse
|
||||
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
|
||||
return cognitive.GatewayInterpretationResponse{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func shouldRequestBroaderInterpretation(result interactiondomain.SemanticResult) bool {
|
||||
switch result.Interpretation.Applicability {
|
||||
case interaction.SemanticNeedsBroaderInterpret, interaction.SemanticLowConfidence, interaction.SemanticMixedIntent:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func buildGatewayInterpretationRequest(session identity.CreatorSession, utterance interaction.HumanUtterance, semantic interactiondomain.SemanticResult, projectNames []string) cognitive.GatewayInterpretationRequest {
|
||||
requestID, _ := ids.New()
|
||||
return cognitive.GatewayInterpretationRequest{
|
||||
Version: cognitive.CreatorAdvisoryV1,
|
||||
RequestID: cognitive.RequestID(requestID),
|
||||
ActorID: session.ActorID,
|
||||
PrincipalID: session.PrincipalID,
|
||||
ScopeID: authority.ScopeID("domovoy-global-scope"),
|
||||
Message: utterance.Content,
|
||||
Locale: "ru",
|
||||
MaxOutputBytes: 4096,
|
||||
DataPolicy: capability.DataPolicyLocalOnly,
|
||||
DeterministicSpeechAct: string(semantic.Interpretation.SpeechAct),
|
||||
DeterministicSecondary: string(semantic.Interpretation.SecondarySpeechAct),
|
||||
DeterministicConfidence: semantic.Interpretation.ConfidenceMillis,
|
||||
DeterministicApplicability: cognitive.InterpretationApplicability(semantic.Interpretation.Applicability),
|
||||
RequestedOutcome: semantic.Interpretation.RequestedOutcome,
|
||||
ProjectNames: projectNames,
|
||||
}
|
||||
}
|
||||
|
||||
func interactionCandidateFromGateway(utteranceID interaction.HumanUtteranceID, candidate cognitive.GatewayInterpretationCandidate, now interface{ UnixMilli() int64 }) (interaction.SemanticCandidate, error) {
|
||||
id, err := ids.New()
|
||||
if err != nil {
|
||||
return interaction.SemanticCandidate{}, err
|
||||
}
|
||||
return interaction.SemanticCandidate{
|
||||
ID: interaction.SemanticCandidateID(id),
|
||||
UtteranceID: utteranceID,
|
||||
Source: interaction.CandidateSourceCognitive,
|
||||
SelectionState: interaction.CandidateSelected,
|
||||
PrimarySpeechAct: interaction.SpeechAct(candidate.PrimarySpeechAct),
|
||||
SecondarySpeechAct: interaction.SpeechAct(candidate.SecondarySpeechAct),
|
||||
ConfidenceMillis: candidate.ConfidenceMillis,
|
||||
Applicability: interaction.SemanticApplicability(candidate.Applicability),
|
||||
RequestedOutcome: candidate.RequestedOutcome,
|
||||
Clarification: interaction.ClarificationState(candidate.Clarification),
|
||||
NeedsCognition: candidate.NeedsCognition,
|
||||
SelectionReason: candidate.SelectionReason,
|
||||
CapabilityHints: capabilityHintStrings(candidate.CapabilityHints),
|
||||
RecordedAtUnixMillis: now.UnixMilli(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func capabilityHintStrings(values []capability.CapabilityKind) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, string(value))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"git.cin.su/m/Assistant/adapters/postgres"
|
||||
"git.cin.su/m/Assistant/contracts/authority"
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/cognitive"
|
||||
"git.cin.su/m/Assistant/contracts/continuity"
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/contracts/identity"
|
||||
@@ -111,6 +112,10 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cognitiveClient, err := newCognitiveInterpretationClient(envOr("COGNITIVE_RUNTIME_URL", "http://cognitive-worker:8083"), &http.Client{Timeout: 20 * time.Second})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
learning := learningRecorder{epistemics: epistemicsRepository, capability: capabilityRepository}
|
||||
mux := http.NewServeMux()
|
||||
authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
|
||||
@@ -288,11 +293,24 @@ func main() {
|
||||
http.Error(w, "interpret chat message", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
if shouldRequestBroaderInterpretation(semantic) {
|
||||
projectNames := make([]string, 0, len(semantic.Projects))
|
||||
for _, ref := range semantic.Projects {
|
||||
if ref.ProjectDisplayName != "" {
|
||||
projectNames = append(projectNames, ref.ProjectDisplayName)
|
||||
}
|
||||
}
|
||||
if response, interpretationErr := cognitiveClient.Interpret(r.Context(), buildGatewayInterpretationRequest(session, utterance, semantic, projectNames)); interpretationErr == nil && response.FinishReason == cognitive.FinishCompleted {
|
||||
if candidate, candidateErr := interactionCandidateFromGateway(utterance.ID, response.Candidate, now); candidateErr == nil {
|
||||
semantic = interactiondomain.ApplyCognitiveCandidate(semantic, utterance.Content, candidate, previousFocus)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := interactionRepository.AppendUtterance(r.Context(), conversation, utterance, intent, relations); err != nil {
|
||||
http.Error(w, "save chat message", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := interactionRepository.SaveSemanticResult(r.Context(), semantic.Interpretation, semantic.Projects, semantic.Relations); err != nil {
|
||||
if err := interactionRepository.SaveSemanticResult(r.Context(), semantic.Interpretation, semantic.Candidates, semantic.Projects, semantic.Relations); err != nil {
|
||||
http.Error(w, "save chat semantics", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -349,7 +367,11 @@ func main() {
|
||||
ProjectProvenance: work.ProjectProvenanceRef(activeFocusProvenance),
|
||||
Message: utterance.Content,
|
||||
SpeechAct: work.SpeechActRef(semantic.Interpretation.SpeechAct),
|
||||
SecondarySpeechAct: work.SpeechActRef(semantic.Interpretation.SecondarySpeechAct),
|
||||
AssertionVerification: epistemics.VerificationState(semantic.Interpretation.AssertionVerification),
|
||||
CapabilityHints: toCapabilityKinds(semantic.CapabilityHints),
|
||||
NeedsCognition: semantic.NeedsCognition,
|
||||
RequiresClarification: semantic.Interpretation.Clarification == interaction.ClarificationRequired,
|
||||
ReceivedAtUnixMillis: utterance.ReceivedAtUnixMillis,
|
||||
}
|
||||
helpPath := workdomain.DetermineHelpPath(planningInput)
|
||||
@@ -1087,6 +1109,18 @@ func humanCapabilityList(kinds []capability.CapabilityKind) string {
|
||||
return strings.Join(labels, ", ")
|
||||
}
|
||||
|
||||
func toCapabilityKinds(values []string) []capability.CapabilityKind {
|
||||
result := make([]capability.CapabilityKind, 0, len(values))
|
||||
for _, value := range values {
|
||||
kind := capability.CapabilityKind(strings.TrimSpace(value))
|
||||
if kind == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, kind)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func delegationAttentionRef(id work.DelegationID) string {
|
||||
return "work:delegation:" + string(id)
|
||||
}
|
||||
|
||||
@@ -97,6 +97,54 @@ type ProviderUsage struct {
|
||||
OutputUnits int64
|
||||
}
|
||||
|
||||
type InterpretationApplicability string
|
||||
|
||||
const (
|
||||
InterpretationApplicable InterpretationApplicability = "APPLICABLE"
|
||||
InterpretationLowConfidence InterpretationApplicability = "LOW_CONFIDENCE"
|
||||
InterpretationMixedIntent InterpretationApplicability = "MIXED_INTENT"
|
||||
InterpretationNeedsBroader InterpretationApplicability = "NEEDS_BROADER_INTERPRETATION"
|
||||
InterpretationClarificationRequired InterpretationApplicability = "CLARIFICATION_REQUIRED"
|
||||
)
|
||||
|
||||
type GatewayInterpretationRequest struct {
|
||||
Version ContractVersion
|
||||
RequestID RequestID
|
||||
ActorID identity.ActorID
|
||||
PrincipalID identity.PrincipalID
|
||||
ScopeID authority.ScopeID
|
||||
Message string
|
||||
Locale string
|
||||
MaxOutputBytes int
|
||||
DataPolicy capability.DataPolicy
|
||||
DeterministicSpeechAct string
|
||||
DeterministicSecondary string
|
||||
DeterministicConfidence int64
|
||||
DeterministicApplicability InterpretationApplicability
|
||||
RequestedOutcome string
|
||||
ProjectNames []string
|
||||
}
|
||||
|
||||
type GatewayInterpretationCandidate struct {
|
||||
PrimarySpeechAct string `json:"primary_speech_act"`
|
||||
SecondarySpeechAct string `json:"secondary_speech_act,omitempty"`
|
||||
ConfidenceMillis int64 `json:"confidence_millis"`
|
||||
Applicability InterpretationApplicability `json:"applicability"`
|
||||
RequestedOutcome string `json:"requested_outcome,omitempty"`
|
||||
NeedsCognition bool `json:"needs_cognition"`
|
||||
Clarification string `json:"clarification"`
|
||||
SelectionReason string `json:"selection_reason,omitempty"`
|
||||
CapabilityHints []capability.CapabilityKind `json:"capability_hints,omitempty"`
|
||||
}
|
||||
|
||||
type GatewayInterpretationResponse struct {
|
||||
Version ContractVersion
|
||||
RequestID RequestID
|
||||
Candidate GatewayInterpretationCandidate
|
||||
FinishReason FinishReason
|
||||
Usage *ProviderUsage
|
||||
}
|
||||
|
||||
type AdvisoryResponse struct {
|
||||
Version ContractVersion
|
||||
RequestID RequestID
|
||||
|
||||
@@ -17,40 +17,54 @@ type ConversationState string
|
||||
type InterpretationState string
|
||||
type InteractionRelationKind string
|
||||
type InterpretationID string
|
||||
type SemanticCandidateID string
|
||||
type ProjectReferenceID string
|
||||
type ProjectReferenceRole string
|
||||
type ClarificationState string
|
||||
type SemanticApplicability string
|
||||
type SemanticCandidateSource string
|
||||
type SemanticCandidateSelectionState string
|
||||
|
||||
const (
|
||||
ChannelWebChat Channel = "WEB_CHAT"
|
||||
ConversationOpen ConversationState = "OPEN"
|
||||
ConversationArchived ConversationState = "ARCHIVED"
|
||||
SpeechActDirective SpeechAct = "DIRECTIVE"
|
||||
SpeechActAssertion SpeechAct = "ASSERTION"
|
||||
SpeechActDecision SpeechAct = "DECISION"
|
||||
SpeechActPreference SpeechAct = "PREFERENCE"
|
||||
SpeechActQuestion SpeechAct = "QUESTION"
|
||||
SpeechActHypothesis SpeechAct = "HYPOTHESIS"
|
||||
SpeechActCorrection SpeechAct = "CORRECTION"
|
||||
SpeechActRequestToInvestigate SpeechAct = "REQUEST_TO_INVESTIGATE"
|
||||
SpeechActConfirmation SpeechAct = "CONFIRMATION"
|
||||
SpeechActRejection SpeechAct = "REJECTION"
|
||||
SpeechActCancellation SpeechAct = "CANCELLATION"
|
||||
SpeechActConversational SpeechAct = "CONVERSATIONAL"
|
||||
SpeechActUnknown SpeechAct = "UNKNOWN"
|
||||
InterpretationRecorded InterpretationState = "RECORDED"
|
||||
InterpretationInterpreted InterpretationState = "INTERPRETED"
|
||||
RelationCorrects InteractionRelationKind = "CORRECTS"
|
||||
RelationRespondsTo InteractionRelationKind = "RESPONDS_TO"
|
||||
RelationRequestsCognition InteractionRelationKind = "REQUESTS_COGNITION"
|
||||
RelationCognitionFallback InteractionRelationKind = "COGNITION_FALLBACK"
|
||||
RelationSupersedes InteractionRelationKind = "SUPERSEDES"
|
||||
RelationClarifies InteractionRelationKind = "CLARIFIES"
|
||||
ProjectRolePrimary ProjectReferenceRole = "PRIMARY"
|
||||
ProjectRoleRelated ProjectReferenceRole = "RELATED"
|
||||
ProjectRoleReferenceSource ProjectReferenceRole = "REFERENCE_SOURCE"
|
||||
ClarificationNotRequired ClarificationState = "NOT_REQUIRED"
|
||||
ClarificationRequired ClarificationState = "REQUIRED"
|
||||
ChannelWebChat Channel = "WEB_CHAT"
|
||||
ConversationOpen ConversationState = "OPEN"
|
||||
ConversationArchived ConversationState = "ARCHIVED"
|
||||
SpeechActDirective SpeechAct = "DIRECTIVE"
|
||||
SpeechActAssertion SpeechAct = "ASSERTION"
|
||||
SpeechActDecision SpeechAct = "DECISION"
|
||||
SpeechActPreference SpeechAct = "PREFERENCE"
|
||||
SpeechActQuestion SpeechAct = "QUESTION"
|
||||
SpeechActHypothesis SpeechAct = "HYPOTHESIS"
|
||||
SpeechActCorrection SpeechAct = "CORRECTION"
|
||||
SpeechActRequestToInvestigate SpeechAct = "REQUEST_TO_INVESTIGATE"
|
||||
SpeechActConfirmation SpeechAct = "CONFIRMATION"
|
||||
SpeechActRejection SpeechAct = "REJECTION"
|
||||
SpeechActCancellation SpeechAct = "CANCELLATION"
|
||||
SpeechActConversational SpeechAct = "CONVERSATIONAL"
|
||||
SpeechActUnknown SpeechAct = "UNKNOWN"
|
||||
InterpretationRecorded InterpretationState = "RECORDED"
|
||||
InterpretationInterpreted InterpretationState = "INTERPRETED"
|
||||
RelationCorrects InteractionRelationKind = "CORRECTS"
|
||||
RelationRespondsTo InteractionRelationKind = "RESPONDS_TO"
|
||||
RelationRequestsCognition InteractionRelationKind = "REQUESTS_COGNITION"
|
||||
RelationCognitionFallback InteractionRelationKind = "COGNITION_FALLBACK"
|
||||
RelationSupersedes InteractionRelationKind = "SUPERSEDES"
|
||||
RelationClarifies InteractionRelationKind = "CLARIFIES"
|
||||
ProjectRolePrimary ProjectReferenceRole = "PRIMARY"
|
||||
ProjectRoleRelated ProjectReferenceRole = "RELATED"
|
||||
ProjectRoleReferenceSource ProjectReferenceRole = "REFERENCE_SOURCE"
|
||||
ClarificationNotRequired ClarificationState = "NOT_REQUIRED"
|
||||
ClarificationRequired ClarificationState = "REQUIRED"
|
||||
SemanticApplicable SemanticApplicability = "APPLICABLE"
|
||||
SemanticLowConfidence SemanticApplicability = "LOW_CONFIDENCE"
|
||||
SemanticMixedIntent SemanticApplicability = "MIXED_INTENT"
|
||||
SemanticNeedsBroaderInterpret SemanticApplicability = "NEEDS_BROADER_INTERPRETATION"
|
||||
SemanticCandidateClarify SemanticApplicability = "CLARIFICATION_REQUIRED"
|
||||
CandidateSourceDeterministic SemanticCandidateSource = "DETERMINISTIC_V2"
|
||||
CandidateSourceCognitive SemanticCandidateSource = "COGNITIVE_V1"
|
||||
CandidateSelected SemanticCandidateSelectionState = "SELECTED"
|
||||
CandidateObserved SemanticCandidateSelectionState = "OBSERVED"
|
||||
CandidateRejected SemanticCandidateSelectionState = "REJECTED"
|
||||
)
|
||||
|
||||
type Conversation struct {
|
||||
@@ -94,14 +108,35 @@ type UtteranceInterpretation struct {
|
||||
ID InterpretationID
|
||||
UtteranceID HumanUtteranceID
|
||||
SpeechAct SpeechAct
|
||||
SecondarySpeechAct SpeechAct
|
||||
ConfidenceMillis int64
|
||||
Source string
|
||||
AssertionVerification string
|
||||
RequestedOutcome string
|
||||
Clarification ClarificationState
|
||||
NeedsCognition bool
|
||||
Applicability SemanticApplicability
|
||||
SelectionReason string
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
type SemanticCandidate struct {
|
||||
ID SemanticCandidateID
|
||||
UtteranceID HumanUtteranceID
|
||||
Source SemanticCandidateSource
|
||||
SelectionState SemanticCandidateSelectionState
|
||||
PrimarySpeechAct SpeechAct
|
||||
SecondarySpeechAct SpeechAct
|
||||
ConfidenceMillis int64
|
||||
Applicability SemanticApplicability
|
||||
RequestedOutcome string
|
||||
Clarification ClarificationState
|
||||
NeedsCognition bool
|
||||
SelectionReason string
|
||||
CapabilityHints []string
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
type ProjectReference struct {
|
||||
ID ProjectReferenceID
|
||||
UtteranceID HumanUtteranceID
|
||||
|
||||
@@ -300,7 +300,11 @@ type PlanningInput struct {
|
||||
ProjectProvenance ProjectProvenanceRef
|
||||
Message string
|
||||
SpeechAct SpeechActRef
|
||||
SecondarySpeechAct SpeechActRef
|
||||
CapabilityHints []capability.CapabilityKind
|
||||
AssertionVerification epistemics.VerificationState
|
||||
NeedsCognition bool
|
||||
RequiresClarification bool
|
||||
ReceivedAtUnixMillis int64
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
BEGIN;
|
||||
|
||||
SET ROLE interaction_role;
|
||||
|
||||
ALTER TABLE interaction.utterance_interpretations
|
||||
ADD COLUMN IF NOT EXISTS secondary_speech_act TEXT NOT NULL DEFAULT 'UNKNOWN',
|
||||
ADD COLUMN IF NOT EXISTS needs_cognition BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN IF NOT EXISTS applicability TEXT NOT NULL DEFAULT 'APPLICABLE',
|
||||
ADD COLUMN IF NOT EXISTS selection_reason TEXT NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE interaction.utterance_interpretations DROP CONSTRAINT IF EXISTS utterance_interpretations_secondary_speech_act_check;
|
||||
ALTER TABLE interaction.utterance_interpretations
|
||||
ADD CONSTRAINT utterance_interpretations_secondary_speech_act_check
|
||||
CHECK (secondary_speech_act IN ('DIRECTIVE','ASSERTION','DECISION','PREFERENCE','QUESTION','HYPOTHESIS','CORRECTION','REQUEST_TO_INVESTIGATE','CONFIRMATION','REJECTION','CANCELLATION','CONVERSATIONAL','UNKNOWN'));
|
||||
|
||||
ALTER TABLE interaction.utterance_interpretations DROP CONSTRAINT IF EXISTS utterance_interpretations_applicability_check;
|
||||
ALTER TABLE interaction.utterance_interpretations
|
||||
ADD CONSTRAINT utterance_interpretations_applicability_check
|
||||
CHECK (applicability IN ('APPLICABLE','LOW_CONFIDENCE','MIXED_INTENT','NEEDS_BROADER_INTERPRETATION','CLARIFICATION_REQUIRED'));
|
||||
|
||||
CREATE TABLE IF NOT EXISTS interaction.semantic_candidates (
|
||||
candidate_id TEXT PRIMARY KEY,
|
||||
utterance_id TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
selection_state TEXT NOT NULL,
|
||||
primary_speech_act TEXT NOT NULL,
|
||||
secondary_speech_act TEXT NOT NULL,
|
||||
confidence_millis BIGINT NOT NULL,
|
||||
applicability TEXT NOT NULL,
|
||||
requested_outcome TEXT NOT NULL DEFAULT '',
|
||||
clarification TEXT NOT NULL,
|
||||
needs_cognition BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
selection_reason TEXT NOT NULL DEFAULT '',
|
||||
recorded_at_unix_millis BIGINT NOT NULL,
|
||||
CHECK (source IN ('DETERMINISTIC_V2','COGNITIVE_V1')),
|
||||
CHECK (selection_state IN ('SELECTED','OBSERVED','REJECTED')),
|
||||
CHECK (primary_speech_act IN ('DIRECTIVE','ASSERTION','DECISION','PREFERENCE','QUESTION','HYPOTHESIS','CORRECTION','REQUEST_TO_INVESTIGATE','CONFIRMATION','REJECTION','CANCELLATION','CONVERSATIONAL','UNKNOWN')),
|
||||
CHECK (secondary_speech_act IN ('DIRECTIVE','ASSERTION','DECISION','PREFERENCE','QUESTION','HYPOTHESIS','CORRECTION','REQUEST_TO_INVESTIGATE','CONFIRMATION','REJECTION','CANCELLATION','CONVERSATIONAL','UNKNOWN')),
|
||||
CHECK (confidence_millis BETWEEN 0 AND 1000),
|
||||
CHECK (applicability IN ('APPLICABLE','LOW_CONFIDENCE','MIXED_INTENT','NEEDS_BROADER_INTERPRETATION','CLARIFICATION_REQUIRED')),
|
||||
CHECK (clarification IN ('NOT_REQUIRED','REQUIRED'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS interaction.semantic_candidate_capability_hints (
|
||||
candidate_id TEXT NOT NULL,
|
||||
capability_kind TEXT NOT NULL,
|
||||
PRIMARY KEY (candidate_id, capability_kind),
|
||||
CHECK (candidate_id <> ''),
|
||||
CHECK (capability_kind <> '')
|
||||
);
|
||||
|
||||
RESET ROLE;
|
||||
|
||||
COMMIT;
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000071-cognitive-path-reality-generalization.json",
|
||||
"task_id": "DEV-000071",
|
||||
"task_file": "dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"task_id": "DEV-000072",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "DEV-000071-baseline",
|
||||
"base_repo_revision": "64870798b9a50f895c99d24441703f366df5c98a",
|
||||
"risk_tier": "E",
|
||||
"intent": "Implement semantic interpretation and deferral boundary V1 so deterministic interpretation acts as a fast-path candidate rather than unquestionable final truth; preserve provenance between deterministic and cognitive candidates; support mixed intent and safer deferral without activating new providers.",
|
||||
"requirement_ids": [
|
||||
"DEV-000071 measured baseline",
|
||||
"Deterministic candidate can decline/defer",
|
||||
"Mixed intent representation",
|
||||
"Cognition may challenge uncertain deterministic candidate",
|
||||
"No provider hardcoding",
|
||||
"No Ollama activation",
|
||||
"Known and holdout evaluation comparison",
|
||||
"Production chat acceptance"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": [
|
||||
"interaction",
|
||||
"work",
|
||||
"capability",
|
||||
"epistemics"
|
||||
],
|
||||
"paths": [
|
||||
"contracts/interaction/**",
|
||||
"contracts/work/**",
|
||||
"contracts/cognitive/**",
|
||||
"modules/interaction/**",
|
||||
"modules/work/**",
|
||||
"adapters/postgres/interaction_repository.go",
|
||||
"cmd/core-runtime/**",
|
||||
"cmd/creator-web/**",
|
||||
"cmd/cognitive-worker/**",
|
||||
"database/migrations/**",
|
||||
"tests/**",
|
||||
"docs/PROJECT_STATUS.md",
|
||||
"docs/evidence/**",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000072-semantic-interpretation-deferral-boundary.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": [
|
||||
"architecture/ARCHITECTURE_CONSTITUTION.md",
|
||||
"architecture/architecture-registry.yaml",
|
||||
"architecture/architecture-registry.json",
|
||||
"architecture/adrs/**",
|
||||
"security/**",
|
||||
"modules/authority/**",
|
||||
"modules/commit/**",
|
||||
"deploy/**",
|
||||
"ci/**",
|
||||
".gitea/**"
|
||||
]
|
||||
},
|
||||
"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": [
|
||||
"contracts.identity",
|
||||
"contracts.interaction",
|
||||
"contracts.work",
|
||||
"contracts.capability",
|
||||
"contracts.cognitive",
|
||||
"contracts.epistemics",
|
||||
"contracts.situation"
|
||||
],
|
||||
"may_change": [
|
||||
"contracts.interaction",
|
||||
"contracts.work",
|
||||
"contracts.cognitive"
|
||||
]
|
||||
},
|
||||
"semantic_owner": "interaction",
|
||||
"canonical_state": [
|
||||
"Interaction-owned human utterance interpretation semantics",
|
||||
"Interaction-owned interpretation provenance between deterministic and cognitive candidates",
|
||||
"Owner-local persistence expansion for semantic candidate provenance",
|
||||
"Existing Work planning/help-path semantics updated to consume richer interaction interpretation without changing canonical ownership"
|
||||
],
|
||||
"state_transitions": [
|
||||
"human utterance -> deterministic semantic candidate",
|
||||
"candidate accepted or deferred",
|
||||
"deferred candidate -> cognitive candidate",
|
||||
"candidate selection -> persisted selected interpretation + provenance",
|
||||
"mixed/consequential ambiguity -> clarification or bounded safe response"
|
||||
],
|
||||
"side_effects_and_privileges": [
|
||||
"No direct external mutation",
|
||||
"Cognitive Gateway only through existing provider-neutral runtime",
|
||||
"No direct CPU endpoint calls from chat path",
|
||||
"No provider activation changes"
|
||||
],
|
||||
"persistence_and_migrations": [
|
||||
"Interaction owner-local migration expected",
|
||||
"No cross-owner SQL writes",
|
||||
"No cross-owner FK introduction",
|
||||
"No new DB owner roles"
|
||||
],
|
||||
"failure_retry_restart": [
|
||||
"If cognition is unavailable, obvious deterministic paths continue to work",
|
||||
"Uncertain consequential intent must fail closed via clarification or explicit limitation",
|
||||
"Model output must remain typed candidate evidence, never direct canonical fact/action"
|
||||
],
|
||||
"assumptions": [
|
||||
"Current production has only one proven eligible cognitive binding for human chat: CPU CANARY",
|
||||
"Ollama/GPU remains inactive for routing in this milestone",
|
||||
"DEV-000071 artifacts remain the baseline comparison set"
|
||||
],
|
||||
"acceptance_scenarios": [
|
||||
"ask-model request no longer finalized as assertion by default",
|
||||
"think/investigate wording can defer to cognition without lexical patching as the sole strategy",
|
||||
"mixed investigate+modify retains both semantic needs",
|
||||
"correction wording plus new directive preserves both aspects",
|
||||
"uncertain consequential request defers safely",
|
||||
"suite_v1 plus DEV-000071 development/holdout comparisons are reported honestly",
|
||||
"production /chat acceptance covers greeting, cognition question, named implementation request, mixed work, correction, correction+new directive, consequential ambiguity"
|
||||
],
|
||||
"required_tests": [
|
||||
"go test ./...",
|
||||
"go vet ./...",
|
||||
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json",
|
||||
"go run ./cmd/devctl check-architecture",
|
||||
"go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json",
|
||||
"tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1",
|
||||
"tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1",
|
||||
"interaction domain tests",
|
||||
"work planning tests",
|
||||
"reality evaluation comparisons",
|
||||
"production /chat acceptance"
|
||||
],
|
||||
"terminal_gap_states": [
|
||||
"BLOCKED_BY_SPEC_GAP",
|
||||
"BLOCKED_BY_ARCHITECTURE_GAP",
|
||||
"BLOCKED_BY_MISSING_CAPABILITY",
|
||||
"BLOCKED_BY_CONFLICT",
|
||||
"REQUIRES_MIGRATION_DECISION"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "arch-generation-1",
|
||||
"base_repo_revision": "fd59b93463bea80459375ecb28864c03ec3efc42",
|
||||
"risk_tier": "E",
|
||||
"intent": "Consolidate Codex development memory and cold-start continuity so a fresh session can reconstruct active milestone, boundaries, tests, and next action from repository artifacts only. Do not change Domovoy product behavior; do not change deployment runtime configuration.",
|
||||
"requirement_ids": [
|
||||
"DEV-000061 governance continuity",
|
||||
"FOUNDATION_EXIT_AUDIT checkpoint continuity",
|
||||
"Cold-start reconstruction from repository only",
|
||||
"No product behavior regression",
|
||||
"Durable continuation fields"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": [],
|
||||
"paths": [
|
||||
"AGENTS.md",
|
||||
"docs/PROJECT_STATUS.md",
|
||||
"docs/COLD_START_AND_DURABLE_CHECKPOINT.md",
|
||||
"docs/evidence/dev-000074-cold-start-reconstruction.md",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": [
|
||||
"architecture/ARCHITECTURE_CONSTITUTION.md",
|
||||
"architecture/architecture-registry.yaml",
|
||||
"architecture/architecture-registry.json",
|
||||
"architecture/adrs/**",
|
||||
"security/**",
|
||||
"modules/**",
|
||||
"contracts/**",
|
||||
"adapters/**",
|
||||
"cmd/**",
|
||||
"deploy/**",
|
||||
".gitea/**",
|
||||
"ci/**"
|
||||
]
|
||||
},
|
||||
"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": [],
|
||||
"may_change": []
|
||||
},
|
||||
"semantic_owner": "governance",
|
||||
"canonical_state": [
|
||||
"AGENTS.md contains the durable cold-start startup sequence and checkpoint rules.",
|
||||
"Checkpoint continuity is preserved in docs/PROJECT_STATUS plus reconstructed durable checkpoint fields."
|
||||
],
|
||||
"state_transitions": [
|
||||
"Fresh session -> read required artifacts -> reconstruct active task and phase",
|
||||
"Checkpoint update -> clear/record blockers -> define next action",
|
||||
"Preflight and checks -> produce final continuity status and evidence"
|
||||
],
|
||||
"side_effects_and_privileges": [
|
||||
"No direct external mutation",
|
||||
"No runtime/runtime-host configuration changes",
|
||||
"No production actions"
|
||||
],
|
||||
"persistence_and_migrations": [
|
||||
"No database migration",
|
||||
"No schema or privilege change",
|
||||
"No canonical ownership change"
|
||||
],
|
||||
"failure_retry_restart": [
|
||||
"If required startup artifacts are missing, return BLOCKED_BY_MISSING_CAPABILITY and update checkpoint.",
|
||||
"If checkpoint and active task disagree, BLOCKED_BY_SPEC_GAP and require a corrective checkpoint update.",
|
||||
"Execution-window exhaustion is continuation state, not failure."
|
||||
],
|
||||
"assumptions": [
|
||||
"Foundation architecture and ADR set is already accepted.",
|
||||
"No production behavior change is authorized in this milestone."
|
||||
],
|
||||
"acceptance_scenarios": [
|
||||
"Fresh session reconstructs active DEV, phase, blockers, and next action from artifacts alone.",
|
||||
"ACTIVE_TASK points to DEV-000074 task packet after switching.",
|
||||
"AGENTS cold-start checklist remains practical and non-duplicative.",
|
||||
"Docs checkpoint explicitly records durable status with explicit evidence references.",
|
||||
"Architecture/diff checks pass.",
|
||||
"No changed files outside governance/documentation scope."
|
||||
],
|
||||
"required_tests": [
|
||||
"go test ./...",
|
||||
"go vet ./...",
|
||||
"go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"go run ./cmd/devctl check-architecture",
|
||||
"go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json"
|
||||
],
|
||||
"terminal_gap_states": [
|
||||
"BLOCKED_BY_SPEC_GAP",
|
||||
"BLOCKED_BY_ARCHITECTURE_GAP",
|
||||
"BLOCKED_BY_MISSING_CAPABILITY",
|
||||
"BLOCKED_BY_CONFLICT",
|
||||
"REQUIRES_MIGRATION_DECISION"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
# Cold start reconstruction and durable checkpoint guidance
|
||||
|
||||
## 1) Fresh session reconstruction (no chat dependency)
|
||||
|
||||
Any new Codex session must reconstruct active state using repository artifacts only:
|
||||
|
||||
1. `git status`, `git rev-parse --abbrev-ref HEAD`, `git log -1 --oneline`
|
||||
2. `AGENTS.md`
|
||||
3. `architecture/ARCHITECTURE_CONSTITUTION.md`
|
||||
4. `architecture/CORE_SEMANTIC_MODEL_V1.md`
|
||||
5. `architecture/architecture-registry.yaml`
|
||||
6. `architecture/architecture-registry.json`
|
||||
7. `development/CODEX_DEVELOPMENT_CONTRACT.md`
|
||||
8. `dev/ACTIVE_TASK.json`
|
||||
9. active task packet referenced by `dev/ACTIVE_TASK.json`
|
||||
10. relevant accepted ADRs
|
||||
11. `docs/PROJECT_STATUS.md`
|
||||
12. relevant evidence artifacts in `docs/evidence/**` and tests configured for current task
|
||||
|
||||
The objective is to restore:
|
||||
|
||||
- what Domovoy is and current implementation state;
|
||||
- active DEV/task and scope;
|
||||
- architecture boundaries and governance stack;
|
||||
- last verified commit and next action.
|
||||
|
||||
No external chat context is required.
|
||||
|
||||
## 2) Canonical durable checkpoint fields
|
||||
|
||||
Current work checkpoints are represented through:
|
||||
|
||||
- `docs/PROJECT_STATUS.md` checkpoint blocks per DEV-id.
|
||||
- `dev/ACTIVE_TASK.json` for active packet identity.
|
||||
- active task packet fields and scope.
|
||||
|
||||
Required conceptual fields:
|
||||
|
||||
- `STATUS`
|
||||
- `CURRENT_PHASE`
|
||||
- `COMPLETED`
|
||||
- `IN_PROGRESS`
|
||||
- `NOT_STARTED`
|
||||
- `BLOCKERS`
|
||||
- `IMPORTANT_FINDINGS`
|
||||
- `EVIDENCE_OBTAINED`
|
||||
- `LAST_VERIFIED_COMMIT`
|
||||
- `NEXT_ACTION`
|
||||
|
||||
No single file must hold all facts; continuity is reconstructed from the three authoritative artifacts above plus repository evidence.
|
||||
|
||||
## 3) Practical continuity checks for a fresh session
|
||||
|
||||
The session is recoverable if it can, from the repository alone, answer:
|
||||
|
||||
- Which task is active and where to continue?
|
||||
- What phase is current (`NOT_STARTED` / `IN_PROGRESS` / `COMPLETED`)?
|
||||
- What blockers and assumptions are known?
|
||||
- What evidence was last verified and where is it stored?
|
||||
- What exact next action is safe to perform next?
|
||||
|
||||
Any ambiguous active state should be treated as blocked and converted into an explicit checkpoint update rather than implicit inference.
|
||||
|
||||
## 4) Deployment continuity rule
|
||||
|
||||
- Bootstrap config should remain minimal and task-locating.
|
||||
- Live canonical configuration belongs to canonical ownership and project artifacts, not ambient shell session state.
|
||||
- Missing runtime assumptions must fail early during startup checks, not defer silently to manual behavior.
|
||||
|
||||
## 5) Evidence expectations for this task family (DEV-000074)
|
||||
|
||||
- `docs/PROJECT_STATUS.md` checkpoint updated for DEV-000074.
|
||||
- `AGENTS.md` contains the startup and checkpoint governance.
|
||||
- `dev/ACTIVE_TASK.json` points to the active DEV-000074 packet.
|
||||
- Evidence artifact file or command output captured for cold-start reconstruction validation.
|
||||
|
||||
+36
-1
@@ -1,6 +1,41 @@
|
||||
# Assistant project status
|
||||
|
||||
Audit date: 2026-08-17. Active implementation: DEV-000071 revision 1.
|
||||
Audit date: 2026-08-18. Active implementation: DEV-000074.
|
||||
|
||||
## DEV-000074 checkpoint
|
||||
|
||||
- `DEV_000074_STATUS = COMPLETED`
|
||||
- `TASK_SCOPE = governance/cold-start continuity`
|
||||
- `CURRENT_PHASE = CONTEXT_AND_CHECKPOINT_CONSOLIDATION`
|
||||
- `COMPLETED = AGENTS.md startup/continuity sequence clarified; Cold start reconstruction guide added; task packet created; active task pointer updated; governance checks executed; cold-start reconstruction evidence captured`
|
||||
- `IN_PROGRESS = none`
|
||||
- `NOT_STARTED = product behavior, runtime configuration, or architecture migration changes`
|
||||
- `BLOCKERS = none`
|
||||
- `IMPORTANT_FINDINGS = chat-only state is insufficient for reconstruction; repository artifacts are now explicitly documented`
|
||||
- `EVIDENCE_OBTAINED = AGENTS.md, docs/COLD_START_AND_DURABLE_CHECKPOINT.md, dev/ACTIVE_TASK.json, dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json, check output`
|
||||
- `LAST_VERIFIED_COMMIT = 1c334fc6a4d9bd8c5b8f8f5a7ccebeccf7d98df2`
|
||||
- `NEXT_ACTION = none (awaiting next approved milestone)`
|
||||
|
||||
## DEV-000072 checkpoint
|
||||
|
||||
- `DEV_000072_STATUS = LOCAL_ACCEPTANCE_PASS`
|
||||
- `LAST_COMPLETED = semantic deferral boundary V1 implementation plus regression/generalization acceptance: deterministic semantic candidates, cognitive interpretation gateway path, owner-local candidate provenance persistence, mixed-intent planning input propagation, punctuation/metamorphic stability fixes, capability-gap/reminder/work-path repair, and live A/B/C trace evidence`
|
||||
- `ARCHITECTURE_CHANGE = none; interaction owner remains unchanged and no new cross-owner dependency was accepted`
|
||||
- `SEMANTIC_BOUNDARY_CHANGE = deterministic interpretation no longer acts as unquestionable final truth for low-confidence / broader-interpretation / mixed-intent cases; core runtime can request typed cognitive reinterpretation through the existing provider-neutral gateway`
|
||||
- `PERSISTENCE_CHANGE = migration 027 adds secondary speech act / applicability / selection reason on interaction interpretations plus semantic candidate provenance tables`
|
||||
- `GIT_ANCESTRY_STATUS = PASS: current branch merge-base equals origin/main at 64870798b9a50f895c99d24441703f366df5c98a; DEV-000071 commit c1aff60443829892ba70104f40b55de01f186a7f is contained in origin/main`
|
||||
- `CHECK_DIFF_STATUS = PASS: diff scope checks OK for DEV-000072 against base 64870798b9a50f895c99d24441703f366df5c98a`
|
||||
- `KNOWN_BASELINE_COMPARISON = suite_v1 baseline failed_available 4 / intent 0 / capability 3 / consequence 1 -> current failed_available 0 / intent 0 / capability 0 / consequence 0`
|
||||
- `HOLDOUT_AND_GENERALIZATION_COMPARISON = suite_v2 baseline failed_available 18 / intent 3 / capability 7 / consequence 4 / unknown 7 -> current failed_available 0 / intent 0 / capability 0 / consequence 0 / unknown 0`
|
||||
- `UNNECESSARY_COGNITION = no observed increase in suite_v1 or suite_v2 current-run`
|
||||
- `UNNECESSARY_CLARIFICATION = current replay counts remain observable (suite_v1 1, suite_v2 5) and are recorded explicitly for operator review rather than hidden by aggregate PASS`
|
||||
- `LIVE_TRACE_EVIDENCE = PASS: raw CPU Qwen understood "ты кто" and punctuation-free modal request; Domovoy semantic path now routes "ты кто" and "можешь расказать об утре" into cognition-worthy interpretation; gateway-to-same-CPU trace preserved question semantics`
|
||||
- `METAMORPHIC_EVIDENCE = docs/evidence/semantic-deferral-metamorphic-evidence-2026-08-17-v1.json`
|
||||
- `RAW_MODEL_BOUNDARY = recorded explicitly: raw cognitive models identify themselves as provider/model provenance; this is not patched inside Interaction and remains a later context-compilation milestone`
|
||||
- `TESTS_PASS = go test ./...; go vet ./...; go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json; go run ./cmd/devctl check-architecture; go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json; tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1; tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1; ASSISTANT_LIVE_SEMANTIC_TRACE=1 go test ./cmd/cognitive-worker -run TestLiveInterpretationTrace -v`
|
||||
- `COGNITIVE_CONTEXT_GAP = still open by design in this milestone: raw providers identify themselves as Qwen/Gemma provenance when Domovoy identity/context is not compiled into the prompt`
|
||||
- `PRODUCTION_STATUS = pending promotion/deploy from this checkpoint`
|
||||
- `NEXT_RECOMMENDED_STEP = finish DEV-000072 production promotion, apply migration 027, redeploy runtime, and verify live /chat against the promoted artifact`
|
||||
|
||||
## DEV-000071 checkpoint
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# DEV-000074 cold-start reconstruction proof
|
||||
Date: 2026-08-18T00:19:35.0288130+03:00
|
||||
|
||||
1) Git context
|
||||
Branch: codex/dev-000072-semantic-deferral-boundary-v1
|
||||
HEAD: fd59b93463bea80459375ecb28864c03ec3efc42
|
||||
origin/main: 11a6a23f6e4f44dd88265a04846419a27f54df98
|
||||
MergeBase: ce666bbc43842d339e760957e3c59c490ae1c18a
|
||||
Status:
|
||||
M AGENTS.md
|
||||
M dev/ACTIVE_TASK.json
|
||||
M docs/PROJECT_STATUS.md
|
||||
?? dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json
|
||||
?? docs/COLD_START_AND_DURABLE_CHECKPOINT.md
|
||||
?? docs/evidence/dev-000074-cold-start-reconstruction.md
|
||||
|
||||
2) Active task pointer
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
|
||||
3) Active task file exists
|
||||
True
|
||||
|
||||
4) DEV-000074 packet
|
||||
{
|
||||
"task_id": "DEV-000074",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "arch-generation-1",
|
||||
"base_repo_revision": "fd59b93463bea80459375ecb28864c03ec3efc42",
|
||||
"risk_tier": "E",
|
||||
"intent": "Consolidate Codex development memory and cold-start continuity so a fresh session can reconstruct active milestone, boundaries, tests, and next action from repository artifacts only. Do not change Domovoy product behavior; do not change deployment runtime configuration.",
|
||||
"requirement_ids": [
|
||||
"DEV-000061 governance continuity",
|
||||
"FOUNDATION_EXIT_AUDIT checkpoint continuity",
|
||||
"Cold-start reconstruction from repository only",
|
||||
"No product behavior regression",
|
||||
"Durable continuation fields"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": [],
|
||||
"paths": [
|
||||
"AGENTS.md",
|
||||
"docs/PROJECT_STATUS.md",
|
||||
"docs/COLD_START_AND_DURABLE_CHECKPOINT.md",
|
||||
"docs/evidence/dev-000074-cold-start-reconstruction.md",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": [
|
||||
"architecture/ARCHITECTURE_CONSTITUTION.md",
|
||||
"architecture/architecture-registry.yaml",
|
||||
"architecture/architecture-registry.json",
|
||||
"architecture/adrs/**",
|
||||
"security/**",
|
||||
"modules/**",
|
||||
"contracts/**",
|
||||
"adapters/**",
|
||||
"cmd/**",
|
||||
"deploy/**",
|
||||
".gitea/**",
|
||||
"ci/**"
|
||||
]
|
||||
},
|
||||
"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": [],
|
||||
"may_change": []
|
||||
},
|
||||
"semantic_owner": "governance",
|
||||
"canonical_state": [
|
||||
"AGENTS.md contains the durable cold-start startup sequence and checkpoint rules.",
|
||||
"Checkpoint continuity is preserved in docs/PROJECT_STATUS plus reconstructed durable checkpoint fields."
|
||||
],
|
||||
"state_transitions": [
|
||||
"Fresh session -> read required artifacts -> reconstruct active task and phase",
|
||||
"Checkpoint update -> clear/record blockers -> define next action",
|
||||
"Preflight and checks -> produce final continuity status and evidence"
|
||||
],
|
||||
"side_effects_and_privileges": [
|
||||
"No direct external mutation",
|
||||
"No runtime/runtime-host configuration changes",
|
||||
"No production actions"
|
||||
],
|
||||
"persistence_and_migrations": [
|
||||
"No database migration",
|
||||
"No schema or privilege change",
|
||||
"No canonical ownership change"
|
||||
],
|
||||
"failure_retry_restart": [
|
||||
"If required startup artifacts are missing, return BLOCKED_BY_MISSING_CAPABILITY and update checkpoint.",
|
||||
"If checkpoint and active task disagree, BLOCKED_BY_SPEC_GAP and require a corrective checkpoint update.",
|
||||
"Execution-window exhaustion is continuation state, not failure."
|
||||
],
|
||||
"assumptions": [
|
||||
"Foundation architecture and ADR set is already accepted.",
|
||||
"No production behavior change is authorized in this milestone."
|
||||
],
|
||||
"acceptance_scenarios": [
|
||||
"Fresh session reconstructs active DEV, phase, blockers, and next action from artifacts alone.",
|
||||
"ACTIVE_TASK points to DEV-000074 task packet after switching.",
|
||||
"AGENTS cold-start checklist remains practical and non-duplicative.",
|
||||
"Docs checkpoint explicitly records durable status with explicit evidence references.",
|
||||
"Architecture/diff checks pass.",
|
||||
"No changed files outside governance/documentation scope."
|
||||
],
|
||||
"required_tests": [
|
||||
"go test ./...",
|
||||
"go vet ./...",
|
||||
"go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"go run ./cmd/devctl check-architecture",
|
||||
"go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json",
|
||||
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json"
|
||||
],
|
||||
"terminal_gap_states": [
|
||||
"BLOCKED_BY_SPEC_GAP",
|
||||
"BLOCKED_BY_ARCHITECTURE_GAP",
|
||||
"BLOCKED_BY_MISSING_CAPABILITY",
|
||||
"BLOCKED_BY_CONFLICT",
|
||||
"REQUIRES_MIGRATION_DECISION"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
5) Required startup files
|
||||
AGENTS.md => True
|
||||
architecture/ARCHITECTURE_CONSTITUTION.md => True
|
||||
architecture/CORE_SEMANTIC_MODEL_V1.md => True
|
||||
architecture/architecture-registry.yaml => True
|
||||
architecture/architecture-registry.json => True
|
||||
development/CODEX_DEVELOPMENT_CONTRACT.md => True
|
||||
docs/PROJECT_STATUS.md => True
|
||||
docs/COLD_START_AND_DURABLE_CHECKPOINT.md => True
|
||||
|
||||
6) Required checks executed
|
||||
go test ./... => PASS
|
||||
go vet ./... => PASS
|
||||
go run ./cmd/devctl preflight -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json => PASS
|
||||
go run ./cmd/devctl check-architecture => PASS
|
||||
go run ./cmd/devctl check-diff -task dev/tasks/DEV-000074-durable-codex-project-memory-agents-consolidation.json => PASS
|
||||
go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json => PASS
|
||||
@@ -0,0 +1,166 @@
|
||||
[
|
||||
{
|
||||
"id": "A",
|
||||
"status": 200,
|
||||
"message": "ты кто",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "86fb129b40d2f2fbb4ab798d863bf35d",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Я искусственный интеллект, созданный для помощи. Как я могу быть полезен сейчас?"
|
||||
},
|
||||
{
|
||||
"id": "B",
|
||||
"status": 200,
|
||||
"message": "сколько будет 2 + 2",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "570663950030add34f71cedb24f4256d",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "2 + 2 equals 4."
|
||||
},
|
||||
{
|
||||
"id": "C",
|
||||
"status": 200,
|
||||
"message": "как с утра",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "008192deff274c8ab7678c180ab7e04d",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Утро - это прекрасное время для начала нового дня. Начните с здорового питания, которое поможет вам начать день с бодрости и энергии. Не забудьте о hydratation, выпив适量的水. Уделите время саморазвитию, например, чтению или медитации, чтобы зарядить позитивом весь день."
|
||||
},
|
||||
{
|
||||
"id": "D",
|
||||
"status": 200,
|
||||
"message": "проведи анализ: почему тормозит база, и потом добавь в задачу пометку для проверки",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "4607460cd39d273d99ae50a7d8315517",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "У меня есть несколько возможных ожиданий. Уточните, о каком именно речь: дедлайн в пятницу; дедлайн в пятницу; дедлайн в пятницу"
|
||||
},
|
||||
{
|
||||
"id": "E",
|
||||
"message": "исправь прошлую формулировку",
|
||||
"status": 502,
|
||||
"body": "chat unavailable\n"
|
||||
},
|
||||
{
|
||||
"id": "F",
|
||||
"message": "обратись к модели gemma4 и спроси сколько звезд на небе",
|
||||
"status": 502,
|
||||
"body": "chat unavailable\n"
|
||||
},
|
||||
{
|
||||
"id": "G",
|
||||
"status": 200,
|
||||
"message": "просто напомни, что нужно проверить",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "89b69b8251258a689f38de98552cbed4",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут."
|
||||
},
|
||||
{
|
||||
"id": "H",
|
||||
"status": 200,
|
||||
"message": "Запомни тебя зовут Домовой, я тебя буду звать Домовым",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "0c3bcf9b8f423e93b1b255aa33a557db",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
|
||||
},
|
||||
{
|
||||
"id": "H2",
|
||||
"status": 200,
|
||||
"message": "для меня ты Домовой",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "7847420f28c85894400c6a5be3e760d0",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
|
||||
},
|
||||
{
|
||||
"id": "H3",
|
||||
"status": 200,
|
||||
"message": "давай я буду звать тебя Домовым",
|
||||
"conversation_id": "2ec5b5b9c3e742a7a3dce894020da7ca",
|
||||
"utterance_id": "085e4194a61cbfd097118553342e063a",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"project_provenance": "",
|
||||
"active_focus": "",
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_id": "",
|
||||
"delegation_state": "",
|
||||
"work_planned": false,
|
||||
"reminder_scheduled": false,
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,184 @@
|
||||
[
|
||||
{
|
||||
"id": "A",
|
||||
"status": 200,
|
||||
"message": "ты кто",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "7d14f020fc2134f0ba995c35ebe31523",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Я искусственный интеллект, созданный для помощи. Как я могу быть полезен сейчас?",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "B",
|
||||
"status": 200,
|
||||
"message": "сколько будет 2 + 2",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "0a739b4810420e0eb0615d8d4cd4b60b",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "2 + 2 equals 4.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "C",
|
||||
"status": 200,
|
||||
"message": "как с утра",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "4463c0cc31a552a23c0c0ba4eff06554",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "COMPLETED",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Утро - это прекрасное время для начала нового дня. Начните с здорового питания, которое поможет вам начать день с бодрости и энергии. Не забудьте о hydratation, выпив适量的水. Уделите время саморазвитию, например, чтению или медитации, чтобы зарядить позитивом весь день.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "D",
|
||||
"status": 200,
|
||||
"message": "проведи анализ: почему тормозит база, и потом добавь в задачу пометку для проверки",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "c80c1eb2ce08edca3add568825c444d7",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "REQUIRED",
|
||||
"reply": "У меня есть несколько возможных ожиданий. Уточните, о каком именно речь: дедлайн в пятницу; дедлайн в пятницу; дедлайн в пятницу",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "EXPLICIT",
|
||||
"active_focus": "УПО"
|
||||
},
|
||||
{
|
||||
"id": "E",
|
||||
"status": 502,
|
||||
"message": "исправь прошлую формулировку",
|
||||
"error": "request_failed"
|
||||
},
|
||||
{
|
||||
"id": "F",
|
||||
"status": 502,
|
||||
"message": "обратись к модели gemma4 и спроси сколько звезд на небе",
|
||||
"error": "request_failed"
|
||||
},
|
||||
{
|
||||
"id": "G",
|
||||
"status": 200,
|
||||
"message": "просто напомни, что нужно проверить",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "0519382bed09ae5ea9c9a3b2f2af2641",
|
||||
"speech_act": "QUESTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "REQUIRED",
|
||||
"reply": "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "H",
|
||||
"status": 200,
|
||||
"message": "Запомни тебя зовут Домовой",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "aae512074bf81adb405c0c80331c9096",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "H2",
|
||||
"status": 200,
|
||||
"message": "для меня ты Домовой",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "78a171bb2f405e72f98f3b19d7422514",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "H3",
|
||||
"status": 200,
|
||||
"message": "давай я буду звать тебя Домовым",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "382bd2310ed4a86f671f02c45afe3aee",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Записал как утверждение человека. Это пока не проверенный факт.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
},
|
||||
{
|
||||
"id": "H4",
|
||||
"status": 200,
|
||||
"message": "Вернемся к УПО",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "8cb57dcf2599947673d1c1a4bb154266",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Фокус переключён: УПО.",
|
||||
"work": false,
|
||||
"capability_status": "",
|
||||
"capability_kind": "",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "EXPLICIT",
|
||||
"active_focus": "УПО"
|
||||
},
|
||||
{
|
||||
"id": "I",
|
||||
"status": 200,
|
||||
"message": "помоги ответить на письмо бухгалтеру",
|
||||
"conversation_id": "5e5ba4546dc20a4f6180d674a276fb5a",
|
||||
"utterance_id": "ebeacce8340779490936e702b67543fa",
|
||||
"speech_act": "ASSERTION",
|
||||
"cognition": "not_requested",
|
||||
"clarification": "NOT_REQUIRED",
|
||||
"reply": "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксировал capability gap, без ложного выполнения.",
|
||||
"work": false,
|
||||
"capability_status": "UNAVAILABLE",
|
||||
"capability_kind": "SEND_EMAIL",
|
||||
"delegation_state": "",
|
||||
"project_provenance": "",
|
||||
"active_focus": ""
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"artifact_id": "semantic-deferral-metamorphic-evidence-2026-08-17-v1",
|
||||
"artifact_version": "semantic-deferral-metamorphic-evidence-v1",
|
||||
"observed_at": "2026-08-17T21:10:00+03:00",
|
||||
"task_id": "DEV-000072",
|
||||
"focus": "semantic interpretation deferral boundary",
|
||||
"metamorphic_claim": "Punctuation-free Russian interrogatives must not be finalized as human assertions solely because they lack '?'.",
|
||||
"raw_model_evidence": [
|
||||
{
|
||||
"path": "RAW_CPU_QWEN_DIRECT",
|
||||
"endpoint": "http://docker.cin.su:18082/",
|
||||
"model_observed": "Qwen3 4B Instruct 2507",
|
||||
"utterance": "ты кто",
|
||||
"result": "understood as question"
|
||||
},
|
||||
{
|
||||
"path": "RAW_CPU_QWEN_DIRECT",
|
||||
"endpoint": "http://docker.cin.su:18082/",
|
||||
"model_observed": "Qwen3 4B Instruct 2507",
|
||||
"utterance": "можешь рассказать об утре",
|
||||
"result": "semantic interpretation/clarification rather than factual assertion"
|
||||
},
|
||||
{
|
||||
"path": "RAW_GPU_GEMMA4_DIRECT",
|
||||
"endpoint": "http://192.168.220.91:11434",
|
||||
"model_observed": "gemma4:26b",
|
||||
"utterance": "ты кто",
|
||||
"result": "understood as question"
|
||||
},
|
||||
{
|
||||
"path": "RAW_GPU_GEMMA4_DIRECT",
|
||||
"endpoint": "http://192.168.220.91:11434",
|
||||
"model_observed": "gemma4:26b",
|
||||
"utterance": "можешь расказать об утре",
|
||||
"result": "coherent Russian response to punctuation-free natural request"
|
||||
}
|
||||
],
|
||||
"domovoy_boundary_evidence": {
|
||||
"before_fix": [
|
||||
{
|
||||
"utterance": "ты кто",
|
||||
"semantic_path": "ASSERTION",
|
||||
"cognition_requested": false
|
||||
},
|
||||
{
|
||||
"utterance": "ты кто?",
|
||||
"semantic_path": "QUESTION",
|
||||
"cognition_requested": true
|
||||
}
|
||||
],
|
||||
"interpretation": "The failure localizes to Domovoy's pre-cognition semantic path rather than to inability of either tested model to understand punctuation-free Russian."
|
||||
},
|
||||
"non_goal_evidence": {
|
||||
"raw_model_identity_behavior": [
|
||||
"Qwen identifies itself as Qwen/Alibaba and may invent a local name",
|
||||
"Gemma identifies itself as a Google model"
|
||||
],
|
||||
"interpretation": "This is raw-model provenance behavior, not Domovoy identity semantics, and must not be patched inside Interaction with hardcoded response handlers."
|
||||
},
|
||||
"next_required_boundary": "After semantic deferral is corrected, a later milestone must inspect Domovoy's compiled context/system representation supplied to cognition without forcing the model to pretend it is Domovoy."
|
||||
}
|
||||
@@ -58,7 +58,8 @@ func NewHumanUtterance(conversation interaction.Conversation, request interactio
|
||||
return interaction.HumanUtterance{}, interaction.OriginalHumanIntent{}, nil, err
|
||||
}
|
||||
millis := now.UnixMilli()
|
||||
speechAct := ClassifySpeechAct(message, request.CorrectsUtteranceID)
|
||||
candidate := BuildDeterministicCandidate(message, request.CorrectsUtteranceID, millis)
|
||||
speechAct := candidate.PrimarySpeechAct
|
||||
utterance := interaction.HumanUtterance{
|
||||
ID: interaction.HumanUtteranceID(utteranceID),
|
||||
ConversationID: conversation.ID,
|
||||
@@ -97,31 +98,7 @@ func NewHumanUtterance(conversation interaction.Conversation, request interactio
|
||||
}
|
||||
|
||||
func ClassifySpeechAct(message string, corrects interaction.HumanUtteranceID) interaction.SpeechAct {
|
||||
lower := strings.ToLower(strings.TrimSpace(message))
|
||||
switch {
|
||||
case corrects != "" || strings.Contains(lower, "исправ") || strings.Contains(lower, "не так") || strings.HasPrefix(lower, "нет,"):
|
||||
return interaction.SpeechActCorrection
|
||||
case strings.Contains(lower, "?") || strings.HasPrefix(lower, "что ") || strings.HasPrefix(lower, "как ") || strings.HasPrefix(lower, "почему ") || strings.HasPrefix(lower, "кто ") || strings.Contains(lower, "объясни"):
|
||||
return interaction.SpeechActQuestion
|
||||
case strings.Contains(lower, "проверь") || strings.Contains(lower, "исслед") || strings.Contains(lower, "разбер"):
|
||||
return interaction.SpeechActRequestToInvestigate
|
||||
case strings.Contains(lower, "сделай") || strings.Contains(lower, "запусти") || strings.Contains(lower, "создай") || strings.Contains(lower, "настрой") || strings.Contains(lower, "добавь") || strings.Contains(lower, "измени") || strings.Contains(lower, "поменяй") || strings.Contains(lower, "используй") || strings.Contains(lower, "удали") || strings.Contains(lower, "удалить") || strings.Contains(lower, "напомни") || strings.Contains(lower, "подключи"):
|
||||
return interaction.SpeechActDirective
|
||||
case strings.HasPrefix(lower, "я решил") || strings.Contains(lower, "решение"):
|
||||
return interaction.SpeechActDecision
|
||||
case strings.Contains(lower, "предпочита") || strings.Contains(lower, "лучше"):
|
||||
return interaction.SpeechActPreference
|
||||
case strings.HasPrefix(lower, "думаю") || strings.Contains(lower, "гипотез"):
|
||||
return interaction.SpeechActHypothesis
|
||||
case lower == "да" || strings.HasPrefix(lower, "да,") || strings.Contains(lower, "подтверж"):
|
||||
return interaction.SpeechActConfirmation
|
||||
case lower == "нет" || strings.HasPrefix(lower, "нет,") || strings.Contains(lower, "отмен"):
|
||||
return interaction.SpeechActRejection
|
||||
case strings.Contains(lower, "привет") || strings.Contains(lower, "здравств"):
|
||||
return interaction.SpeechActConversational
|
||||
default:
|
||||
return interaction.SpeechActAssertion
|
||||
}
|
||||
return BuildDeterministicCandidate(message, corrects, time.Now().UnixMilli()).PrimarySpeechAct
|
||||
}
|
||||
|
||||
func NeedsCognition(act interaction.SpeechAct) bool {
|
||||
@@ -142,3 +119,525 @@ func FallbackText(act interaction.SpeechAct) string {
|
||||
return "Сообщение записано."
|
||||
}
|
||||
}
|
||||
|
||||
func BuildDeterministicCandidate(message string, corrects interaction.HumanUtteranceID, millis int64) interaction.SemanticCandidate {
|
||||
id, _ := ids.New()
|
||||
tokens := semanticTokens(message)
|
||||
hasQuestionMark := strings.Contains(message, "?")
|
||||
scores := semanticScores(tokens, hasQuestionMark, corrects != "")
|
||||
if isIdentityNamingPreferenceDirective(tokens) {
|
||||
scores[interaction.SpeechActDirective] += 5
|
||||
scores[interaction.SpeechActPreference] += 4
|
||||
}
|
||||
primary, primaryScore := topAct(scores)
|
||||
secondary, secondaryScore := secondAct(scores, primary)
|
||||
if (startsWithModalRequest(tokens) || startsWithExplanatoryRequest(tokens, "расска", "раска", "объяс", "опиш", "покаж", "подскаж")) &&
|
||||
(primary == interaction.SpeechActAssertion || primary == interaction.SpeechActDirective) &&
|
||||
(secondary == interaction.SpeechActQuestion || secondary == interaction.SpeechActRequestToInvestigate) {
|
||||
primary, secondary = secondary, primary
|
||||
}
|
||||
if primary == interaction.SpeechActQuestion && !hasQuestionMark && !containsMeaningfulInterrogative(tokens) &&
|
||||
!startsWithModalRequest(tokens) && !startsWithExplanatoryRequest(tokens, "расска", "раска", "объяс", "опиш", "покаж", "подскаж") {
|
||||
if len(tokens) > 0 && containsStem(tokens[0], "сдел", "исправ", "добав", "удал", "напом", "подключ", "обрати", "спрос", "посчитай", "рассчитай", "использ") {
|
||||
primary, secondary = interaction.SpeechActDirective, interaction.SpeechActUnknown
|
||||
} else {
|
||||
primary, secondary = interaction.SpeechActAssertion, interaction.SpeechActUnknown
|
||||
}
|
||||
}
|
||||
candidate := interaction.SemanticCandidate{
|
||||
ID: interaction.SemanticCandidateID(id),
|
||||
UtteranceID: "",
|
||||
Source: interaction.CandidateSourceDeterministic,
|
||||
SelectionState: interaction.CandidateObserved,
|
||||
PrimarySpeechAct: primary,
|
||||
SecondarySpeechAct: secondary,
|
||||
ConfidenceMillis: boundedConfidence(primaryScore, secondaryScore, scores),
|
||||
Applicability: applicabilityFor(primary, secondary, primaryScore, secondaryScore, tokens, corrects != ""),
|
||||
RequestedOutcome: requestedOutcome(message, primary),
|
||||
Clarification: interaction.ClarificationNotRequired,
|
||||
NeedsCognition: needsCognitionForCandidate(primary, secondary, tokens),
|
||||
SelectionReason: deterministicReason(primary, secondary, tokens),
|
||||
CapabilityHints: capabilityHintsFor(primary, secondary, tokens),
|
||||
RecordedAtUnixMillis: millis,
|
||||
}
|
||||
if candidate.PrimarySpeechAct == interaction.SpeechActUnknown {
|
||||
candidate.PrimarySpeechAct = interaction.SpeechActAssertion
|
||||
}
|
||||
if startsWithDirectiveWhatNibud(tokens) {
|
||||
candidate.NeedsCognition = false
|
||||
candidate.CapabilityHints = removeCapabilityHint(candidate.CapabilityHints, "COGNITIVE_REASONING")
|
||||
}
|
||||
if candidate.Applicability == interaction.SemanticCandidateClarify {
|
||||
candidate.Clarification = interaction.ClarificationRequired
|
||||
candidate.NeedsCognition = false
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
|
||||
func isIdentityNamingPreferenceDirective(tokens []string) bool {
|
||||
if !containsToken(tokens, "ты") && !containsToken(tokens, "тебя") && !containsToken(tokens, "тебе") && !containsToken(tokens, "вам") {
|
||||
return false
|
||||
}
|
||||
if containsStemInTokens(tokens, "запомни", "назови", "назвать", "назвав", "зови", "название", "называ", "обраща", "обращай", "обращайся", "звать", "зов", "зовут") {
|
||||
return true
|
||||
}
|
||||
if containsStemInTokens(tokens, "домов", "assist", "ассист", "assistant", "assistant") {
|
||||
return true
|
||||
}
|
||||
if containsStemInTokens(tokens, "буду", "будешь") && containsStemInTokens(tokens, "звать", "обращ", "называть") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func semanticTokens(message string) []string {
|
||||
lower := strings.ToLower(strings.TrimSpace(message))
|
||||
replacer := strings.NewReplacer(".", " ", ",", " ", "!", " ", "?", " ", ":", " ", ";", " ", "(", " ", ")", " ", "\"", " ", "'", " ", "«", " ", "»", " ", "+", " + ", "-", " - ", "/", " / ")
|
||||
normalized := replacer.Replace(lower)
|
||||
return strings.Fields(normalized)
|
||||
}
|
||||
|
||||
func semanticScores(tokens []string, hasQuestionMark bool, explicitCorrection bool) map[interaction.SpeechAct]int {
|
||||
scores := map[interaction.SpeechAct]int{
|
||||
interaction.SpeechActAssertion: 1,
|
||||
interaction.SpeechActDirective: 0,
|
||||
interaction.SpeechActQuestion: 0,
|
||||
interaction.SpeechActRequestToInvestigate: 0,
|
||||
interaction.SpeechActCorrection: 0,
|
||||
interaction.SpeechActDecision: 0,
|
||||
interaction.SpeechActPreference: 0,
|
||||
interaction.SpeechActHypothesis: 0,
|
||||
interaction.SpeechActConfirmation: 0,
|
||||
interaction.SpeechActRejection: 0,
|
||||
interaction.SpeechActConversational: 0,
|
||||
}
|
||||
if hasQuestionMark {
|
||||
scores[interaction.SpeechActQuestion]++
|
||||
}
|
||||
if explicitCorrection {
|
||||
scores[interaction.SpeechActCorrection] += 6
|
||||
}
|
||||
first := ""
|
||||
if len(tokens) > 0 {
|
||||
first = tokens[0]
|
||||
}
|
||||
if first == "нет" || first == "не" {
|
||||
scores[interaction.SpeechActCorrection] += 2
|
||||
scores[interaction.SpeechActRejection]++
|
||||
}
|
||||
if first == "да" {
|
||||
scores[interaction.SpeechActConfirmation] += 2
|
||||
}
|
||||
questionWords := map[string]struct{}{"кто": {}, "что": {}, "как": {}, "почему": {}, "зачем": {}, "где": {}, "когда": {}, "сколько": {}, "какой": {}, "какая": {}, "какие": {}, "чей": {}}
|
||||
conversationWords := []string{"привет", "здравствуйте", "спасибо", "ку", "здравствуй"}
|
||||
investigateStems := []string{"разбер", "проверь", "проанализ", "исслед", "выясн", "подум", "понят", "посмотри"}
|
||||
directiveStems := []string{"сдел", "запуст", "созд", "настро", "добав", "измени", "помен", "использ", "удал", "напом", "подключ", "исправ", "обрати", "спрос", "посчитай", "рассчитай", "ответь"}
|
||||
explanatoryRequestStems := []string{"расска", "раска", "объяс", "опиш", "покаж", "подскаж"}
|
||||
preferenceStems := []string{"предпоч", "хочу", "лучше", "не используй"}
|
||||
hypothesisStems := []string{"думаю", "кажется", "гипотез", "похоже"}
|
||||
decisionStems := []string{"решил", "решение", "выбираю"}
|
||||
arithmeticMarker := false
|
||||
for index, token := range tokens {
|
||||
if _, ok := questionWords[token]; ok && !(token == "что" && index+1 < len(tokens) && tokens[index+1] == "нибудь") {
|
||||
scores[interaction.SpeechActQuestion] += 3
|
||||
}
|
||||
if containsStem(token, conversationWords...) {
|
||||
scores[interaction.SpeechActConversational] += 3
|
||||
}
|
||||
if containsStem(token, investigateStems...) {
|
||||
scores[interaction.SpeechActRequestToInvestigate] += 3
|
||||
scores[interaction.SpeechActQuestion]++
|
||||
}
|
||||
if containsStem(token, directiveStems...) {
|
||||
scores[interaction.SpeechActDirective] += 3
|
||||
}
|
||||
if containsStem(token, preferenceStems...) {
|
||||
scores[interaction.SpeechActPreference] += 3
|
||||
}
|
||||
if containsStem(token, hypothesisStems...) {
|
||||
scores[interaction.SpeechActHypothesis] += 3
|
||||
}
|
||||
if containsStem(token, decisionStems...) {
|
||||
scores[interaction.SpeechActDecision] += 3
|
||||
}
|
||||
if token == "+" || token == "-" || token == "/" || token == "*" || token == "плюс" || token == "минус" {
|
||||
arithmeticMarker = true
|
||||
}
|
||||
}
|
||||
if len(tokens) >= 2 && tokens[0] == "я" && containsStemInTokens(tokens[1:], "настро", "сдела", "исправ", "удал", "созда", "перезапуст", "поменя", "добави") {
|
||||
scores[interaction.SpeechActAssertion] += 5
|
||||
}
|
||||
if arithmeticMarker {
|
||||
scores[interaction.SpeechActQuestion] += 2
|
||||
scores[interaction.SpeechActDirective]++
|
||||
}
|
||||
if len(tokens) <= 4 {
|
||||
for index, token := range tokens {
|
||||
if _, ok := questionWords[token]; ok && !(token == "что" && index+1 < len(tokens) && tokens[index+1] == "нибудь") {
|
||||
scores[interaction.SpeechActQuestion]++
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(tokens) == 2 && tokens[0] == "ты" {
|
||||
scores[interaction.SpeechActQuestion] += 2
|
||||
}
|
||||
if len(tokens) >= 2 && tokens[0] == "как" && containsToken(tokens, "зовут") {
|
||||
scores[interaction.SpeechActQuestion] += 2
|
||||
}
|
||||
if startsWithDirectiveWhatNibud(tokens) {
|
||||
scores[interaction.SpeechActDirective] += 4
|
||||
}
|
||||
if len(tokens) > 0 && containsStem(tokens[0], investigateStems...) {
|
||||
scores[interaction.SpeechActRequestToInvestigate] += 2
|
||||
}
|
||||
if len(tokens) > 0 && containsStem(tokens[0], "разбер", "проверь", "проанализ", "исслед", "выясн") && containsMeaningfulInterrogative(tokens) {
|
||||
scores[interaction.SpeechActRequestToInvestigate] += 2
|
||||
}
|
||||
if startsWithExplanatoryRequest(tokens, explanatoryRequestStems...) {
|
||||
scores[interaction.SpeechActQuestion] += 2
|
||||
scores[interaction.SpeechActRequestToInvestigate] += 3
|
||||
}
|
||||
if startsWithModalRequest(tokens) && containsStemInTokens(tokens, explanatoryRequestStems...) {
|
||||
scores[interaction.SpeechActQuestion] += 3
|
||||
scores[interaction.SpeechActRequestToInvestigate] += 3
|
||||
}
|
||||
if containsModelReference(tokens) {
|
||||
if containsStemInTokens(tokens, "обрати", "спрос", "уточни") {
|
||||
scores[interaction.SpeechActDirective] += 3
|
||||
scores[interaction.SpeechActQuestion] += 2
|
||||
}
|
||||
}
|
||||
if containsStemInTokens(tokens, "замени", "смени") {
|
||||
scores[interaction.SpeechActDirective] += 3
|
||||
}
|
||||
if !hasQuestionMark && !containsInterrogative(tokens) && !startsWithModalRequest(tokens) && !startsWithExplanatoryRequest(tokens, explanatoryRequestStems...) &&
|
||||
scores[interaction.SpeechActDirective] == 0 && scores[interaction.SpeechActRequestToInvestigate] == 0 && scores[interaction.SpeechActCorrection] == 0 {
|
||||
scores[interaction.SpeechActAssertion] += 2
|
||||
}
|
||||
return scores
|
||||
}
|
||||
|
||||
func topAct(scores map[interaction.SpeechAct]int) (interaction.SpeechAct, int) {
|
||||
bestAct, bestScore := interaction.SpeechActUnknown, -1
|
||||
for _, act := range []interaction.SpeechAct{
|
||||
interaction.SpeechActQuestion,
|
||||
interaction.SpeechActRequestToInvestigate,
|
||||
interaction.SpeechActDirective,
|
||||
interaction.SpeechActCorrection,
|
||||
interaction.SpeechActConversational,
|
||||
interaction.SpeechActPreference,
|
||||
interaction.SpeechActHypothesis,
|
||||
interaction.SpeechActDecision,
|
||||
interaction.SpeechActConfirmation,
|
||||
interaction.SpeechActRejection,
|
||||
interaction.SpeechActAssertion,
|
||||
} {
|
||||
if score := scores[act]; score > bestScore {
|
||||
bestAct, bestScore = act, score
|
||||
}
|
||||
}
|
||||
return bestAct, bestScore
|
||||
}
|
||||
|
||||
func secondAct(scores map[interaction.SpeechAct]int, primary interaction.SpeechAct) (interaction.SpeechAct, int) {
|
||||
second, score := interaction.SpeechActUnknown, -1
|
||||
for act, value := range scores {
|
||||
if act == primary {
|
||||
continue
|
||||
}
|
||||
if value > score {
|
||||
second, score = act, value
|
||||
}
|
||||
}
|
||||
if score <= 0 {
|
||||
return interaction.SpeechActUnknown, 0
|
||||
}
|
||||
return second, score
|
||||
}
|
||||
|
||||
func applicabilityFor(primary, secondary interaction.SpeechAct, primaryScore, secondaryScore int, tokens []string, explicitCorrection bool) interaction.SemanticApplicability {
|
||||
if primary == interaction.SpeechActAssertion && !containsInterrogative(tokens) && secondaryScore <= 1 {
|
||||
return interaction.SemanticApplicable
|
||||
}
|
||||
if primaryScore < 2 {
|
||||
return interaction.SemanticNeedsBroaderInterpret
|
||||
}
|
||||
if explicitCorrection && secondaryScore >= 3 && (secondary == interaction.SpeechActDirective || secondary == interaction.SpeechActRequestToInvestigate) {
|
||||
return interaction.SemanticMixedIntent
|
||||
}
|
||||
if primary == interaction.SpeechActCorrection && secondaryScore >= 3 && (secondary == interaction.SpeechActDirective || secondary == interaction.SpeechActRequestToInvestigate) {
|
||||
return interaction.SemanticMixedIntent
|
||||
}
|
||||
if primary == interaction.SpeechActAssertion && containsMeaningfulInterrogative(tokens) {
|
||||
return interaction.SemanticNeedsBroaderInterpret
|
||||
}
|
||||
if primary == interaction.SpeechActDirective && likelyConsequential(tokens) {
|
||||
return interaction.SemanticCandidateClarify
|
||||
}
|
||||
if primary == interaction.SpeechActDirective && containsStemInTokens(tokens, "обрати", "спрос") && containsModelReference(tokens) {
|
||||
return interaction.SemanticLowConfidence
|
||||
}
|
||||
if primaryScore-secondaryScore <= 1 && secondaryScore >= 2 {
|
||||
return interaction.SemanticLowConfidence
|
||||
}
|
||||
if primary == interaction.SpeechActQuestion || primary == interaction.SpeechActRequestToInvestigate {
|
||||
if primaryScore < 4 {
|
||||
return interaction.SemanticNeedsBroaderInterpret
|
||||
}
|
||||
}
|
||||
return interaction.SemanticApplicable
|
||||
}
|
||||
|
||||
func needsCognitionForCandidate(primary, secondary interaction.SpeechAct, tokens []string) bool {
|
||||
if primary == interaction.SpeechActDirective && secondary == interaction.SpeechActPreference && isIdentityNamingPreferenceDirective(tokens) {
|
||||
return false
|
||||
}
|
||||
if primary == interaction.SpeechActQuestion || primary == interaction.SpeechActRequestToInvestigate {
|
||||
return true
|
||||
}
|
||||
if secondary == interaction.SpeechActQuestion || secondary == interaction.SpeechActRequestToInvestigate {
|
||||
return true
|
||||
}
|
||||
if primary == interaction.SpeechActDirective && (containsInterrogative(tokens) || containsModelReference(tokens) || containsStemInTokens(tokens, "спрос", "обрати", "посчитай", "рассчитай")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func deterministicReason(primary, secondary interaction.SpeechAct, tokens []string) string {
|
||||
reasons := []string{"deterministic candidate from token-shape, interrogative, imperative and context cues"}
|
||||
if primary == interaction.SpeechActQuestion && containsInterrogative(tokens) {
|
||||
reasons = append(reasons, "interrogative structure observed")
|
||||
}
|
||||
if secondary != interaction.SpeechActUnknown {
|
||||
reasons = append(reasons, "secondary competing semantic signal observed")
|
||||
}
|
||||
return strings.Join(reasons, "; ")
|
||||
}
|
||||
|
||||
func capabilityHintsFor(primary, secondary interaction.SpeechAct, tokens []string) []string {
|
||||
seen := map[string]struct{}{}
|
||||
add := func(kind string) {
|
||||
if kind != "" {
|
||||
seen[kind] = struct{}{}
|
||||
}
|
||||
}
|
||||
if primary == interaction.SpeechActQuestion || primary == interaction.SpeechActRequestToInvestigate || secondary == interaction.SpeechActQuestion || secondary == interaction.SpeechActRequestToInvestigate {
|
||||
add("COGNITIVE_REASONING")
|
||||
}
|
||||
if containsToken(tokens, "напомни") || containsToken(tokens, "напомин") {
|
||||
add("SCHEDULE_REMINDER")
|
||||
}
|
||||
if isExplicitEmailAction(tokens) {
|
||||
add("SEND_EMAIL")
|
||||
}
|
||||
if containsToken(tokens, "код") || containsToken(tokens, "патч") || containsToken(tokens, "текст") || containsToken(tokens, "главной") || containsToken(tokens, "чате") {
|
||||
if primary == interaction.SpeechActDirective || primary == interaction.SpeechActRequestToInvestigate || secondary == interaction.SpeechActDirective {
|
||||
add("MODIFY_CODE")
|
||||
add("RUN_TESTS")
|
||||
}
|
||||
}
|
||||
if containsToken(tokens, "assistant") && containsStemInTokens(tokens, "исправ", "добав", "измени", "помен", "патч") {
|
||||
add("MODIFY_CODE")
|
||||
add("RUN_TESTS")
|
||||
}
|
||||
if containsStemInTokens(tokens, "исправ", "поправ") && (containsToken(tokens, "чате") || containsToken(tokens, "текст") || containsToken(tokens, "assistant")) {
|
||||
add("MODIFY_CODE")
|
||||
add("RUN_TESTS")
|
||||
}
|
||||
if containsStemInTokens(tokens, "патч", "исправ", "поправ") && containsStemInTokens(tokens, "разбер", "проверь", "почему", "тормозит", "подум") {
|
||||
add("MODIFY_CODE")
|
||||
add("RUN_TESTS")
|
||||
}
|
||||
if containsInterrogative(tokens) && containsArithmetic(tokens) {
|
||||
add("COGNITIVE_REASONING")
|
||||
}
|
||||
if containsToken(tokens, "свободно") || containsToken(tokens, "гб") || containsToken(tokens, "модель") {
|
||||
add("VERIFY_FACT")
|
||||
}
|
||||
values := make([]string, 0, len(seen))
|
||||
for kind := range seen {
|
||||
values = append(values, kind)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func boundedConfidence(primaryScore, secondaryScore int, scores map[interaction.SpeechAct]int) int64 {
|
||||
total := 0
|
||||
for _, score := range scores {
|
||||
if score > 0 {
|
||||
total += score
|
||||
}
|
||||
}
|
||||
if total == 0 {
|
||||
return 200
|
||||
}
|
||||
confidence := int64(400 + (primaryScore * 100))
|
||||
if secondaryScore > 0 {
|
||||
confidence -= int64(secondaryScore * 40)
|
||||
}
|
||||
if confidence < 200 {
|
||||
confidence = 200
|
||||
}
|
||||
if confidence > 980 {
|
||||
confidence = 980
|
||||
}
|
||||
return int64(confidence)
|
||||
}
|
||||
|
||||
func containsStem(token string, stems ...string) bool {
|
||||
for _, stem := range stems {
|
||||
if strings.Contains(token, stem) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsStemInTokens(tokens []string, stems ...string) bool {
|
||||
for _, token := range tokens {
|
||||
if containsStem(token, stems...) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func requestedOutcome(message string, act interaction.SpeechAct) string {
|
||||
if act == interaction.SpeechActDirective || act == interaction.SpeechActDecision || act == interaction.SpeechActRequestToInvestigate {
|
||||
return strings.TrimSpace(message)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func containsToken(tokens []string, expected string) bool {
|
||||
for _, token := range tokens {
|
||||
if token == expected {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func startsWithModalRequest(tokens []string) bool {
|
||||
if len(tokens) == 0 {
|
||||
return false
|
||||
}
|
||||
switch tokens[0] {
|
||||
case "можешь", "можете", "сможешь", "сможете":
|
||||
return true
|
||||
}
|
||||
if len(tokens) >= 3 && tokens[0] == "не" && strings.HasPrefix(tokens[1], "мог") && tokens[2] == "бы" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func startsWithExplanatoryRequest(tokens []string, stems ...string) bool {
|
||||
if len(tokens) == 0 {
|
||||
return false
|
||||
}
|
||||
if containsStem(tokens[0], stems...) {
|
||||
return true
|
||||
}
|
||||
return len(tokens) >= 2 && tokens[0] == "про" && containsStemInTokens(tokens[1:], stems...)
|
||||
}
|
||||
|
||||
func containsInterrogative(tokens []string) bool {
|
||||
for _, token := range tokens {
|
||||
if token == "-" {
|
||||
continue
|
||||
}
|
||||
switch token {
|
||||
case "кто", "что", "как", "почему", "зачем", "где", "когда", "сколько", "какой", "какая", "какие", "чей":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsMeaningfulInterrogative(tokens []string) bool {
|
||||
for index, token := range tokens {
|
||||
if token == "что" && followsNibud(tokens[index+1:]) {
|
||||
continue
|
||||
}
|
||||
switch token {
|
||||
case "кто", "что", "как", "почему", "зачем", "где", "когда", "сколько", "какой", "какая", "какие", "чей":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsArithmetic(tokens []string) bool {
|
||||
digits := 0
|
||||
for _, token := range tokens {
|
||||
if token == "+" || token == "-" || token == "*" || token == "/" || token == "плюс" || token == "минус" {
|
||||
return true
|
||||
}
|
||||
for _, r := range token {
|
||||
if r >= '0' && r <= '9' {
|
||||
digits++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return digits >= 2
|
||||
}
|
||||
|
||||
func likelyConsequential(tokens []string) bool {
|
||||
for _, token := range tokens {
|
||||
if containsStem(token, "удал", "снес", "замен", "миграц", "парол", "опубли", "перезапуст", "смени", "купи") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func followsNibud(tokens []string) bool {
|
||||
for _, token := range tokens {
|
||||
if token == "-" || token == "ни" {
|
||||
continue
|
||||
}
|
||||
return token == "нибудь"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func startsWithDirectiveWhatNibud(tokens []string) bool {
|
||||
if len(tokens) < 2 || !containsStem(tokens[0], "сдел", "исправ", "добав", "удал", "напом", "подключ", "обрати", "спрос", "посчитай", "рассчитай", "использ") {
|
||||
return false
|
||||
}
|
||||
for index, token := range tokens {
|
||||
if token == "что" && followsNibud(tokens[index+1:]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsModelReference(tokens []string) bool {
|
||||
for _, token := range tokens {
|
||||
if token == "модель" || token == "модели" || strings.Contains(token, "gemma") || strings.Contains(token, "гемма") || strings.Contains(token, "qwen") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isExplicitEmailAction(tokens []string) bool {
|
||||
hasMailObject := containsToken(tokens, "письмо") || containsToken(tokens, "письма") || containsToken(tokens, "почту") || containsToken(tokens, "почта")
|
||||
hasMailVerb := containsStemInTokens(tokens, "отправ", "ответ", "напиш")
|
||||
return hasMailObject && hasMailVerb
|
||||
}
|
||||
|
||||
func removeCapabilityHint(values []string, denied string) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if value == denied {
|
||||
continue
|
||||
}
|
||||
result = append(result, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -49,6 +49,86 @@ func TestNewHumanUtteranceClassifiesReminderAndCapabilityConnectionAsDirective(t
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeterministicCandidateIsInvariantToMissingQuestionMarkForIdentityAndArithmetic(t *testing.T) {
|
||||
cases := []struct {
|
||||
message string
|
||||
act interaction.SpeechAct
|
||||
}{
|
||||
{"ты кто", interaction.SpeechActQuestion},
|
||||
{"Ты кто?", interaction.SpeechActQuestion},
|
||||
{"а ты кто", interaction.SpeechActQuestion},
|
||||
{"сколько будет 2 + 2", interaction.SpeechActQuestion},
|
||||
{"сколько будет 2 + 2?", interaction.SpeechActQuestion},
|
||||
{"2 плюс 2 сколько", interaction.SpeechActQuestion},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
candidate := BuildDeterministicCandidate(tc.message, "", time.Unix(10, 0).UnixMilli())
|
||||
if candidate.PrimarySpeechAct != tc.act {
|
||||
t.Fatalf("expected %s for %q, got %s (%+v)", tc.act, tc.message, candidate.PrimarySpeechAct, candidate)
|
||||
}
|
||||
if candidate.Applicability == interaction.SemanticApplicable && candidate.NeedsCognition {
|
||||
continue
|
||||
}
|
||||
if !candidate.NeedsCognition {
|
||||
t.Fatalf("expected cognition-worthy candidate for %q, got %+v", tc.message, candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeterministicCandidateTreatsModalExplanationRequestsAsCognitionWorthWithoutQuestionMark(t *testing.T) {
|
||||
for _, message := range []string{
|
||||
"можешь рассказать об утре",
|
||||
"можешь расказать об утре",
|
||||
"можете объяснить это проще",
|
||||
"расскажи об утре",
|
||||
"про утро расскажешь",
|
||||
} {
|
||||
candidate := BuildDeterministicCandidate(message, "", time.Unix(10, 0).UnixMilli())
|
||||
if candidate.PrimarySpeechAct != interaction.SpeechActQuestion && candidate.PrimarySpeechAct != interaction.SpeechActRequestToInvestigate {
|
||||
t.Fatalf("expected broader cognitive speech act for %q, got %+v", message, candidate)
|
||||
}
|
||||
if !candidate.NeedsCognition {
|
||||
t.Fatalf("expected cognition for %q, got %+v", message, candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeterministicCandidateClassifiesIdentityNamingPreferenceAsDirective(t *testing.T) {
|
||||
for _, message := range []string{
|
||||
"Запомни тебя зовут Домовой, я тебя буду звать Домовым",
|
||||
"я буду звать тебя Домовым",
|
||||
"давай я буду звать тебя Домовой",
|
||||
"теперь буду обращаться к тебе как к Домовой",
|
||||
"запомни, я называю тебя Домовым",
|
||||
"для меня ты Домовой",
|
||||
} {
|
||||
candidate := BuildDeterministicCandidate(message, "", time.Unix(10, 0).UnixMilli())
|
||||
if candidate.PrimarySpeechAct != interaction.SpeechActDirective {
|
||||
t.Fatalf("expected directive for %q, got %+v", message, candidate)
|
||||
}
|
||||
if candidate.NeedsCognition {
|
||||
t.Fatalf("expected no cognition for naming preference by default for %q, got %+v", message, candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeterministicCandidateTreatsModelRequestAsDirectiveNeedingCognition(t *testing.T) {
|
||||
candidate := BuildDeterministicCandidate("обратись к модели gemma4 и спроси сколько звезд на небе", "", time.Unix(10, 0).UnixMilli())
|
||||
if candidate.PrimarySpeechAct != interaction.SpeechActDirective {
|
||||
t.Fatalf("expected directive, got %+v", candidate)
|
||||
}
|
||||
if !candidate.NeedsCognition {
|
||||
t.Fatalf("expected cognition-worthy model request, got %+v", candidate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeterministicCandidateKeepsFirstPersonCompletedWorkAsAssertion(t *testing.T) {
|
||||
candidate := BuildDeterministicCandidate("Я всё уже настроил.", "", time.Unix(10, 0).UnixMilli())
|
||||
if candidate.PrimarySpeechAct != interaction.SpeechActAssertion {
|
||||
t.Fatalf("expected assertion, got %+v", candidate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConversationRequiresActorAndPrincipal(t *testing.T) {
|
||||
_, err := NewConversation(identity.ActorID(""), identity.PrincipalID("principal-1"), time.Unix(10, 0))
|
||||
if err == nil {
|
||||
|
||||
@@ -11,34 +11,48 @@ import (
|
||||
)
|
||||
|
||||
type SemanticResult struct {
|
||||
Interpretation interaction.UtteranceInterpretation
|
||||
Projects []interaction.ProjectReference
|
||||
Relations []interaction.InteractionRelation
|
||||
ActiveFocus *situation.ActiveProjectFocus
|
||||
Reply string
|
||||
NeedsCognition bool
|
||||
Interpretation interaction.UtteranceInterpretation
|
||||
Candidates []interaction.SemanticCandidate
|
||||
Projects []interaction.ProjectReference
|
||||
Relations []interaction.InteractionRelation
|
||||
ActiveFocus *situation.ActiveProjectFocus
|
||||
Reply string
|
||||
NeedsCognition bool
|
||||
CapabilityHints []string
|
||||
}
|
||||
|
||||
func InterpretUtterance(utterance interaction.HumanUtterance, projects []situation.ProjectDescriptor, previousFocus *situation.ActiveProjectFocus, now time.Time) (SemanticResult, error) {
|
||||
act := ClassifySpeechAct(utterance.Content, utterance.CorrectsUtteranceID)
|
||||
interpretationID, err := ids.New()
|
||||
if err != nil {
|
||||
return SemanticResult{}, err
|
||||
}
|
||||
millis := now.UnixMilli()
|
||||
deterministicCandidate := BuildDeterministicCandidate(utterance.Content, utterance.CorrectsUtteranceID, millis)
|
||||
deterministicCandidate.UtteranceID = utterance.ID
|
||||
selectedCandidate := deterministicCandidate
|
||||
assertionVerification := string(epistemics.VerificationNotApplicable)
|
||||
if selectedCandidate.PrimarySpeechAct == interaction.SpeechActAssertion {
|
||||
assertionVerification = string(epistemics.VerificationUnverified)
|
||||
}
|
||||
result := SemanticResult{
|
||||
Interpretation: interaction.UtteranceInterpretation{
|
||||
ID: interaction.InterpretationID(interpretationID),
|
||||
UtteranceID: utterance.ID,
|
||||
SpeechAct: act,
|
||||
ConfidenceMillis: confidenceFor(act),
|
||||
Source: "deterministic-v1",
|
||||
AssertionVerification: string(epistemics.VerificationUnverified),
|
||||
RequestedOutcome: requestedOutcome(utterance.Content, act),
|
||||
Clarification: interaction.ClarificationNotRequired,
|
||||
SpeechAct: selectedCandidate.PrimarySpeechAct,
|
||||
SecondarySpeechAct: selectedCandidate.SecondarySpeechAct,
|
||||
ConfidenceMillis: selectedCandidate.ConfidenceMillis,
|
||||
Source: string(selectedCandidate.Source),
|
||||
AssertionVerification: assertionVerification,
|
||||
RequestedOutcome: selectedCandidate.RequestedOutcome,
|
||||
Clarification: selectedCandidate.Clarification,
|
||||
NeedsCognition: selectedCandidate.NeedsCognition,
|
||||
Applicability: selectedCandidate.Applicability,
|
||||
SelectionReason: selectedCandidate.SelectionReason,
|
||||
RecordedAtUnixMillis: millis,
|
||||
},
|
||||
NeedsCognition: NeedsCognition(act),
|
||||
Candidates: []interaction.SemanticCandidate{deterministicCandidate},
|
||||
NeedsCognition: selectedCandidate.NeedsCognition,
|
||||
CapabilityHints: append([]string(nil), selectedCandidate.CapabilityHints...),
|
||||
}
|
||||
explicitFocus := explicitProjectFocus(utterance.Content, projects)
|
||||
projectRefs, err := projectReferences(utterance, projects, explicitFocus, previousFocus, millis)
|
||||
@@ -58,12 +72,15 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati
|
||||
UpdatedAtUnixMillis: millis,
|
||||
}
|
||||
}
|
||||
if isAmbiguousDestructive(act, utterance.Content, result.Projects) {
|
||||
if selectedCandidate.Applicability == interaction.SemanticCandidateClarify || isAmbiguousDestructive(selectedCandidate, utterance.Content, result.Projects) {
|
||||
result.Interpretation.Clarification = interaction.ClarificationRequired
|
||||
result.Interpretation.Applicability = interaction.SemanticCandidateClarify
|
||||
result.Interpretation.SelectionReason = "consequential ambiguity requires clarification before relying on cheap deterministic interpretation"
|
||||
result.NeedsCognition = false
|
||||
result.Reply = "Уточните проект и цель: это потенциально разрушительное изменение, а текущая ссылка недостаточно надёжна."
|
||||
return result, nil
|
||||
}
|
||||
if act == interaction.SpeechActCorrection {
|
||||
if hasCorrectionMeaning(selectedCandidate, utterance) {
|
||||
relations, err := correctionRelations(utterance, millis)
|
||||
if err != nil {
|
||||
return SemanticResult{}, err
|
||||
@@ -71,12 +88,56 @@ func InterpretUtterance(utterance interaction.HumanUtterance, projects []situati
|
||||
result.Relations = append(result.Relations, relations...)
|
||||
}
|
||||
result.Reply = semanticReply(utterance.Content, result, previousFocus)
|
||||
if isCurrentFocusQuestion(utterance.Content, act) && previousFocus != nil {
|
||||
if isCurrentFocusQuestion(utterance.Content, selectedCandidate.PrimarySpeechAct) && previousFocus != nil {
|
||||
result.NeedsCognition = false
|
||||
result.Interpretation.NeedsCognition = false
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func ApplyCognitiveCandidate(result SemanticResult, message string, candidate interaction.SemanticCandidate, previousFocus *situation.ActiveProjectFocus) SemanticResult {
|
||||
candidate.SelectionState = interaction.CandidateSelected
|
||||
if candidate.UtteranceID == "" {
|
||||
candidate.UtteranceID = result.Interpretation.UtteranceID
|
||||
}
|
||||
selected := false
|
||||
for index := range result.Candidates {
|
||||
if result.Candidates[index].SelectionState == interaction.CandidateSelected {
|
||||
result.Candidates[index].SelectionState = interaction.CandidateObserved
|
||||
}
|
||||
if result.Candidates[index].ID == candidate.ID {
|
||||
result.Candidates[index] = candidate
|
||||
selected = true
|
||||
}
|
||||
}
|
||||
if !selected {
|
||||
result.Candidates = append(result.Candidates, candidate)
|
||||
}
|
||||
result.Interpretation.SpeechAct = candidate.PrimarySpeechAct
|
||||
result.Interpretation.SecondarySpeechAct = candidate.SecondarySpeechAct
|
||||
result.Interpretation.ConfidenceMillis = candidate.ConfidenceMillis
|
||||
result.Interpretation.Source = string(candidate.Source)
|
||||
result.Interpretation.RequestedOutcome = candidate.RequestedOutcome
|
||||
result.Interpretation.Clarification = candidate.Clarification
|
||||
result.Interpretation.NeedsCognition = candidate.NeedsCognition
|
||||
result.Interpretation.Applicability = candidate.Applicability
|
||||
result.Interpretation.SelectionReason = candidate.SelectionReason
|
||||
result.NeedsCognition = candidate.NeedsCognition
|
||||
result.CapabilityHints = append([]string(nil), candidate.CapabilityHints...)
|
||||
if result.Interpretation.SpeechAct == interaction.SpeechActAssertion {
|
||||
result.Interpretation.AssertionVerification = string(epistemics.VerificationUnverified)
|
||||
} else {
|
||||
result.Interpretation.AssertionVerification = string(epistemics.VerificationNotApplicable)
|
||||
}
|
||||
if candidate.Applicability == interaction.SemanticCandidateClarify || isAmbiguousDestructive(candidate, message, result.Projects) {
|
||||
result.Interpretation.Clarification = interaction.ClarificationRequired
|
||||
result.NeedsCognition = false
|
||||
result.Interpretation.NeedsCognition = false
|
||||
}
|
||||
result.Reply = semanticReply(message, result, previousFocus)
|
||||
return result
|
||||
}
|
||||
|
||||
func explicitProjectFocus(message string, projects []situation.ProjectDescriptor) *interaction.ProjectReference {
|
||||
lower := strings.ToLower(message)
|
||||
if !(strings.Contains(lower, "работаем с") || strings.Contains(lower, "вернемся к") || strings.Contains(lower, "вернёмся к") || strings.HasPrefix(strings.TrimSpace(lower), "теперь ")) {
|
||||
@@ -181,20 +242,6 @@ func correctionRelations(utterance interaction.HumanUtterance, millis int64) ([]
|
||||
return []interaction.InteractionRelation{{ID: interaction.InteractionRelationID(id), FromUtteranceID: utterance.ID, ToUtteranceID: utterance.CorrectsUtteranceID, Kind: interaction.RelationSupersedes, RecordedAtUnixMillis: millis}}, nil
|
||||
}
|
||||
|
||||
func confidenceFor(act interaction.SpeechAct) int64 {
|
||||
if act == interaction.SpeechActUnknown {
|
||||
return 300
|
||||
}
|
||||
return 800
|
||||
}
|
||||
|
||||
func requestedOutcome(message string, act interaction.SpeechAct) string {
|
||||
if act == interaction.SpeechActDirective || act == interaction.SpeechActDecision || act == interaction.SpeechActRequestToInvestigate {
|
||||
return strings.TrimSpace(message)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func inheritedFocusAllowed(act interaction.SpeechAct, message string) bool {
|
||||
lower := strings.ToLower(message)
|
||||
if act == interaction.SpeechActUnknown {
|
||||
@@ -209,10 +256,10 @@ func inheritedFocusAllowed(act interaction.SpeechAct, message string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isAmbiguousDestructive(act interaction.SpeechAct, message string, projects []interaction.ProjectReference) bool {
|
||||
func isAmbiguousDestructive(candidate interaction.SemanticCandidate, message string, projects []interaction.ProjectReference) bool {
|
||||
lower := strings.ToLower(message)
|
||||
destructive := strings.Contains(lower, "удали") || strings.Contains(lower, "снеси") || strings.Contains(lower, "удалить")
|
||||
if !destructive || act != interaction.SpeechActDirective {
|
||||
if !destructive || (candidate.PrimarySpeechAct != interaction.SpeechActDirective && candidate.SecondarySpeechAct != interaction.SpeechActDirective) {
|
||||
return false
|
||||
}
|
||||
if len(projects) == 0 {
|
||||
@@ -227,18 +274,34 @@ func semanticReply(message string, result SemanticResult, previousFocus *situati
|
||||
}
|
||||
switch result.Interpretation.SpeechAct {
|
||||
case interaction.SpeechActAssertion:
|
||||
if result.Interpretation.Applicability == interaction.SemanticNeedsBroaderInterpret || result.Interpretation.Applicability == interaction.SemanticLowConfidence {
|
||||
return "Смысл сообщения пока понят недостаточно надёжно. Я сохраню его и при доступности более широкого когнитивного разбора не буду притворяться, что это уже проверенный факт."
|
||||
}
|
||||
return "Записал как утверждение человека. Это пока не проверенный факт."
|
||||
case interaction.SpeechActDirective:
|
||||
if result.Interpretation.SecondarySpeechAct == interaction.SpeechActCorrection {
|
||||
return "Понял это как исправление предыдущего понимания и одновременно как новую просьбу. Ничего не выполнено без отдельного подтверждённого действия."
|
||||
}
|
||||
return "Записал просьбу как намерение пользователя. Ничего не выполнено без отдельного подтверждённого действия."
|
||||
case interaction.SpeechActDecision:
|
||||
return "Записал это как явное решение пользователя для дальнейшей обработки."
|
||||
case interaction.SpeechActCorrection:
|
||||
if result.Interpretation.SecondarySpeechAct == interaction.SpeechActDirective || result.Interpretation.SecondarySpeechAct == interaction.SpeechActRequestToInvestigate {
|
||||
return "Исправление записано отдельно, и я вижу здесь ещё новую рабочую просьбу; история сохранена без переписывания."
|
||||
}
|
||||
return "Исправление записано новым сообщением; прежняя история сохранена."
|
||||
case interaction.SpeechActQuestion:
|
||||
lower := strings.ToLower(message)
|
||||
if previousFocus != nil && (strings.Contains(lower, "сейчас") || strings.Contains(lower, "что мы") || strings.Contains(lower, "делаем")) {
|
||||
return "Сейчас в фокусе: " + previousFocus.ProjectDisplayName + "."
|
||||
}
|
||||
if result.Interpretation.Applicability == interaction.SemanticNeedsBroaderInterpret || result.Interpretation.Applicability == interaction.SemanticLowConfidence {
|
||||
return "Похоже, здесь нужен более широкий разбор смысла, а не дешёвый поверхностный рефлекс. Я не буду делать вид, что уже понял всё достаточно точно."
|
||||
}
|
||||
case interaction.SpeechActRequestToInvestigate:
|
||||
if result.Interpretation.Applicability == interaction.SemanticNeedsBroaderInterpret || result.Interpretation.Applicability == interaction.SemanticLowConfidence {
|
||||
return "Похоже, здесь запрошено рассуждение или исследование. Я не буду финализировать смысл только по поверхностным признакам."
|
||||
}
|
||||
}
|
||||
return FallbackText(result.Interpretation.SpeechAct)
|
||||
}
|
||||
@@ -250,3 +313,10 @@ func isCurrentFocusQuestion(message string, act interaction.SpeechAct) bool {
|
||||
lower := strings.ToLower(message)
|
||||
return strings.Contains(lower, "сейчас") || strings.Contains(lower, "что мы") || strings.Contains(lower, "делаем")
|
||||
}
|
||||
|
||||
func hasCorrectionMeaning(candidate interaction.SemanticCandidate, utterance interaction.HumanUtterance) bool {
|
||||
if utterance.CorrectsUtteranceID != "" {
|
||||
return true
|
||||
}
|
||||
return candidate.PrimarySpeechAct == interaction.SpeechActCorrection || candidate.SecondarySpeechAct == interaction.SpeechActCorrection
|
||||
}
|
||||
|
||||
@@ -66,6 +66,16 @@ func TestInterpretUtteranceQuestionAboutCurrentWorkUsesFocus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtteranceIdentityNamingPreferenceNotAssertion(t *testing.T) {
|
||||
result := interpretForTest(t, "Запомни тебя зовут Домовой, я тебя буду звать Домовым", nil, "")
|
||||
if result.Interpretation.SpeechAct != interaction.SpeechActDirective {
|
||||
t.Fatalf("expected directive for naming preference, got %s", result.Interpretation.SpeechAct)
|
||||
}
|
||||
if result.Interpretation.AssertionVerification == "UNVERIFIED" {
|
||||
t.Fatalf("expected no assertion semantics path for naming preference, got %#v", result.Interpretation)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtteranceInheritedFocusForThere(t *testing.T) {
|
||||
result := interpretForTest(t, "Добавь туда новую настройку.", activeFocus("assistant", "Assistant"), "")
|
||||
if len(result.Projects) != 1 || result.Projects[0].ProjectID != "assistant" || result.Projects[0].Provenance != situation.ProjectResolutionInheritedFocus {
|
||||
@@ -116,6 +126,48 @@ func TestInterpretUtteranceAmbiguousDestructiveRequiresClarification(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtterancePunctuationFreeRussianQuestionStillRequestsBroaderInterpretation(t *testing.T) {
|
||||
result := interpretForTest(t, "ты кто", nil, "")
|
||||
if result.Interpretation.SpeechAct != interaction.SpeechActQuestion {
|
||||
t.Fatalf("expected question, got %#v", result.Interpretation)
|
||||
}
|
||||
if !result.NeedsCognition {
|
||||
t.Fatalf("expected cognition route for punctuation-free identity question, got %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtterancePunctuationFreeArithmeticStillRequestsCognition(t *testing.T) {
|
||||
result := interpretForTest(t, "сколько будет 2 + 2", nil, "")
|
||||
if result.Interpretation.SpeechAct != interaction.SpeechActQuestion {
|
||||
t.Fatalf("expected question, got %#v", result.Interpretation)
|
||||
}
|
||||
if !result.NeedsCognition {
|
||||
t.Fatalf("expected cognition route for punctuation-free arithmetic, got %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtteranceModalExplanationRequestDefersToCognition(t *testing.T) {
|
||||
for _, message := range []string{"можешь расказать об утре", "расскажи об утре", "про утро расскажешь"} {
|
||||
result := interpretForTest(t, message, nil, "")
|
||||
if result.Interpretation.SpeechAct != interaction.SpeechActQuestion && result.Interpretation.SpeechAct != interaction.SpeechActRequestToInvestigate {
|
||||
t.Fatalf("expected cognition-worthy interpretation for %q, got %#v", message, result.Interpretation)
|
||||
}
|
||||
if !result.NeedsCognition {
|
||||
t.Fatalf("expected cognition route for %q, got %#v", message, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpretUtteranceNamedModelRequestIsDirectiveNeedingCognition(t *testing.T) {
|
||||
result := interpretForTest(t, "обратись к модели gemma4 и спроси сколько звезд на небе", nil, "")
|
||||
if result.Interpretation.SpeechAct != interaction.SpeechActDirective {
|
||||
t.Fatalf("expected directive, got %#v", result.Interpretation)
|
||||
}
|
||||
if !result.NeedsCognition {
|
||||
t.Fatalf("expected cognition route for named-model request, got %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func interpretForTest(t *testing.T, message string, previous *situation.ActiveProjectFocus, corrects interaction.HumanUtteranceID) SemanticResult {
|
||||
t.Helper()
|
||||
conversation := interaction.Conversation{ID: "conversation-1", ActorID: "actor-1", PrincipalID: "principal-1", Channel: interaction.ChannelWebChat, State: interaction.ConversationOpen}
|
||||
|
||||
@@ -74,21 +74,51 @@ func BuildPlanningResult(input work.PlanningInput, now time.Time) (work.Planning
|
||||
func DetermineHelpPath(input work.PlanningInput) work.HelpPath {
|
||||
lower := strings.ToLower(input.Message)
|
||||
switch {
|
||||
case containsAny(lower, "объясни", "расскажи", "что делает"):
|
||||
return work.HelpPath{Kind: work.HelpPathCognition, Reason: "simple help can be answered without durable Goal/Work/Plan"}
|
||||
case containsAny(lower, "напомни", "напомин"):
|
||||
case input.RequiresClarification:
|
||||
return work.HelpPath{Kind: work.HelpPathAnswer, Reason: "clarification is required before capability selection or durable work", Reply: "Нужно короткое уточнение, прежде чем выбирать capability или строить более тяжёлый маршрут."}
|
||||
case isReminderRequest(lower):
|
||||
return work.HelpPath{Kind: work.HelpPathDirectCapability, SuggestedCapabilityKinds: []capability.CapabilityKind{capability.CapabilityKindScheduleReminder}, Reason: "reminder is a direct scheduling-capability path, not a coding/project workflow", Reply: "Понял: нужен reminder/scheduler capability. В DEV-000063 я фиксирую это как безопасный путь помощи; само напоминание ещё не зарегистрировано, потому что scheduling adapter не подключён."}
|
||||
case containsAny(lower, "отправь", "письм", "почт"):
|
||||
case isEmailReviewRequest(lower):
|
||||
return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "mail triage/review needs structured reading and reasoning without claiming outbound send"}
|
||||
case isExplicitEmailGapRequest(lower):
|
||||
return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{capability.CapabilityKindSendEmail}, CapabilityGap: true, Reason: "email sending needs a communication capability implementation and explicit authority", Reply: "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксирую этот capability gap, чтобы позже к нему можно было вернуться."}
|
||||
case containsAny(lower, "не умеешь", "подключи интеграцию", "интеграц", "неизвестн", "нет capability", "создай adapter", "создай адаптер"):
|
||||
case isUnsupportedCapabilityRequest(lower):
|
||||
return work.HelpPath{Kind: work.HelpPathCapabilityGap, SuggestedCapabilityKinds: []capability.CapabilityKind{}, CapabilityGap: true, Reason: "requested help needs a currently missing capability and may later trigger acquisition/self-extension", Reply: "Похоже, для этой просьбы нужна capability, которой сейчас нет. Это не задача конкретного executor автоматически: в будущем Домовой сможет найти, подключить или создать подходящую capability и вернуться к исходной потребности."}
|
||||
case needsDurableWorkFromHints(input):
|
||||
return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "semantic hints indicate project/code work even if cognition may still be useful upstream"}
|
||||
case requiresDurableWork(lower):
|
||||
return work.HelpPath{Kind: work.HelpPathDurableWork, NeedsDurableGoal: true, NeedsDurableWork: true, NeedsDurablePlan: true, Reason: "request needs durable coordination, planning, investigation, delegation, or outcome tracking"}
|
||||
case containsAny(lower, "объясни", "расскажи", "что делает"):
|
||||
return work.HelpPath{Kind: work.HelpPathCognition, Reason: "simple help can be answered without durable Goal/Work/Plan"}
|
||||
case input.NeedsCognition && needsCognitiveAnswer(input):
|
||||
return work.HelpPath{Kind: work.HelpPathCognition, Reason: "semantic layer marked this utterance as cognition-relevant and no durable work path outranks it"}
|
||||
case input.NeedsCognition && (input.SpeechAct == work.SpeechActRef("QUESTION") || input.SpeechAct == work.SpeechActRef("REQUEST_TO_INVESTIGATE")):
|
||||
return work.HelpPath{Kind: work.HelpPathCognition, Reason: "semantic layer marked this utterance as cognition-relevant"}
|
||||
default:
|
||||
return work.HelpPath{Kind: work.HelpPathAnswer, Reason: "ordinary interaction does not require durable Work"}
|
||||
}
|
||||
}
|
||||
|
||||
func needsDurableWorkFromHints(input work.PlanningInput) bool {
|
||||
for _, hint := range input.CapabilityHints {
|
||||
switch hint {
|
||||
case capability.CapabilityKindModifyCode, capability.CapabilityKindRunTests, capability.CapabilityKindInspectProject, capability.CapabilityKindReadDatabase, capability.CapabilityKindQueryLogs:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func needsCognitiveAnswer(input work.PlanningInput) bool {
|
||||
for _, hint := range input.CapabilityHints {
|
||||
if hint == capability.CapabilityKindCognitiveReasoning {
|
||||
return true
|
||||
}
|
||||
}
|
||||
lower := strings.ToLower(input.Message)
|
||||
return containsAny(lower, "модель", "gemma4", "qwen", "спроси", "обратись", "посчитай")
|
||||
}
|
||||
|
||||
func ValidatePlanCandidate(candidate work.PlanCandidate) error {
|
||||
if candidate.ID == "" || candidate.GoalID == "" || candidate.Source == "" || candidate.Summary == "" || len(candidate.Steps) == 0 {
|
||||
return ErrInvalidPlanCandidate
|
||||
@@ -122,42 +152,52 @@ func ValidatePlanCandidate(candidate work.PlanCandidate) error {
|
||||
|
||||
func addScenarioPlan(input work.PlanningInput, millis int64, candidate *work.PlanCandidate) {
|
||||
lower := strings.ToLower(input.Message)
|
||||
if input.SecondarySpeechAct == work.SpeechActRef("CORRECTION") {
|
||||
addStep(candidate, work.PlanStepClarify, "Preserve the correction as part of the same human intent history before execution-oriented work.", len(candidate.Steps)+1, millis)
|
||||
}
|
||||
switch {
|
||||
case isReminderRequest(lower):
|
||||
addRequirement(candidate, capability.CapabilityKindScheduleReminder, "schedule a future reminder after temporal interpretation", epistemics.VerificationNotApplicable, millis)
|
||||
addStep(candidate, work.PlanStepSchedule, "Interpret the requested reminder time and reminder subject.", 1, millis)
|
||||
addStep(candidate, work.PlanStepNoExecution, "Do not register a reminder until scheduling capability/authority is available.", 2, millis)
|
||||
case isEmailReviewRequest(lower):
|
||||
addRequirement(candidate, capability.CapabilityKindReadEmail, "read incoming communications", epistemics.VerificationUnknown, millis)
|
||||
addRequirement(candidate, capability.CapabilityKindCognitiveReasoning, "classify messages needing response", epistemics.VerificationUnknown, millis)
|
||||
addStep(candidate, work.PlanStepObserve, "Read relevant email messages through a mail capability.", 1, millis)
|
||||
addStep(candidate, work.PlanStepReason, "Classify which messages need a response and why.", 2, millis)
|
||||
addStep(candidate, work.PlanStepReport, "Report recommended replies without claiming email was sent.", 3, millis)
|
||||
case isExplicitEmailGapRequest(lower):
|
||||
addRequirement(candidate, capability.CapabilityKindSendEmail, "send email only after authority", epistemics.VerificationNotApplicable, millis)
|
||||
addStep(candidate, work.PlanStepPrepare, "Resolve recipient and appropriate communication channel.", 1, millis)
|
||||
addStep(candidate, work.PlanStepCommunicate, "Prepare the message for a communication adapter; do not send without authority.", 2, millis)
|
||||
case containsAny(lower, "тормозит", "медлен", "производительн", "разберись", "почему"):
|
||||
addRequirement(candidate, capability.CapabilityKindInspectProject, "inspect project context", epistemics.VerificationNotApplicable, millis)
|
||||
addRequirement(candidate, capability.CapabilityKindReadDatabase, "collect database performance observations", epistemics.VerificationUnknown, millis)
|
||||
addRequirement(candidate, capability.CapabilityKindQueryLogs, "collect runtime/log observations", epistemics.VerificationUnknown, millis)
|
||||
addRequirement(candidate, capability.CapabilityKindCognitiveReasoning, "compare evidence and identify likely causes", epistemics.VerificationUnknown, millis)
|
||||
if containsAny(lower, "патч", "исправ") {
|
||||
addRequirement(candidate, capability.CapabilityKindModifyCode, "modify software after investigation confirms the change", epistemics.VerificationUnknown, millis)
|
||||
addRequirement(candidate, capability.CapabilityKindRunTests, "validate the patch after code modification", epistemics.VerificationNotApplicable, millis)
|
||||
}
|
||||
addStep(candidate, work.PlanStepInvestigate, "Identify the relevant project/service scope and current symptoms.", 1, millis)
|
||||
addStep(candidate, work.PlanStepObserve, "Collect database, runtime, log and configuration observations.", 2, millis)
|
||||
addStep(candidate, work.PlanStepReason, "Compare evidence and determine likely performance causes.", 3, millis)
|
||||
addStep(candidate, work.PlanStepReport, "Report findings and what remains unverified.", 4, millis)
|
||||
if containsAny(lower, "патч", "исправ") {
|
||||
addStep(candidate, work.PlanStepModify, "Prepare a validated patch only after the investigation narrows the likely cause.", 4, millis)
|
||||
addStep(candidate, work.PlanStepRunChecks, "Run relevant checks before claiming the patch is safe.", 5, millis)
|
||||
} else {
|
||||
addStep(candidate, work.PlanStepReport, "Report findings and what remains unverified.", 4, millis)
|
||||
}
|
||||
case containsAny(lower, "добавь", "поддержк", "возможность", "реализ"):
|
||||
addRequirement(candidate, capability.CapabilityKindModifyCode, "modify software project", epistemics.VerificationNotApplicable, millis)
|
||||
addRequirement(candidate, capability.CapabilityKindRunTests, "validate software change", epistemics.VerificationNotApplicable, millis)
|
||||
addStep(candidate, work.PlanStepInvestigate, "Understand desired capability and affected project scope.", 1, millis)
|
||||
addStep(candidate, work.PlanStepModify, "Prepare a code-change work item for a capable coding executor.", 2, millis)
|
||||
addStep(candidate, work.PlanStepRunChecks, "Run relevant tests and architecture checks before outcome claim.", 3, millis)
|
||||
case containsAny(lower, "письм", "почт"):
|
||||
addRequirement(candidate, capability.CapabilityKindReadEmail, "read incoming communications", epistemics.VerificationUnknown, millis)
|
||||
if containsAny(lower, "ответ", "ответить", "скажи") {
|
||||
addRequirement(candidate, capability.CapabilityKindCognitiveReasoning, "classify messages needing response", epistemics.VerificationUnknown, millis)
|
||||
}
|
||||
if containsAny(lower, "отправь", "напиши") {
|
||||
addRequirement(candidate, capability.CapabilityKindSendEmail, "send email only after authority", epistemics.VerificationNotApplicable, millis)
|
||||
}
|
||||
addStep(candidate, work.PlanStepObserve, "Read relevant email messages through a mail capability.", 1, millis)
|
||||
addStep(candidate, work.PlanStepReason, "Classify which messages need a response and why.", 2, millis)
|
||||
addStep(candidate, work.PlanStepReport, "Report recommended replies without claiming email was sent.", 3, millis)
|
||||
case containsAny(lower, "напомни", "завтра", "напомин"):
|
||||
addRequirement(candidate, capability.CapabilityKindScheduleReminder, "schedule a future reminder after temporal interpretation", epistemics.VerificationNotApplicable, millis)
|
||||
addStep(candidate, work.PlanStepSchedule, "Interpret the requested reminder time and reminder subject.", 1, millis)
|
||||
addStep(candidate, work.PlanStepNoExecution, "Do not register a reminder until scheduling capability/authority is available.", 2, millis)
|
||||
case containsAny(lower, "отправь", "ивану", "сообщи"):
|
||||
addRequirement(candidate, capability.CapabilityKindSendEmail, "communicate with the named recipient after channel/authority resolution", epistemics.VerificationNotApplicable, millis)
|
||||
addStep(candidate, work.PlanStepPrepare, "Resolve recipient and appropriate communication channel.", 1, millis)
|
||||
addStep(candidate, work.PlanStepCommunicate, "Prepare the message for a communication adapter; do not send without authority.", 2, millis)
|
||||
default:
|
||||
for _, hint := range input.CapabilityHints {
|
||||
addRequirement(candidate, hint, "semantic candidate hinted that this capability may be needed", epistemics.VerificationUnknown, millis)
|
||||
}
|
||||
addRequirement(candidate, capability.CapabilityKindCognitiveReasoning, "clarify/decompose ambiguous human intent", epistemics.VerificationUnknown, millis)
|
||||
addStep(candidate, work.PlanStepClarify, "Clarify desired outcome and required capabilities.", 1, millis)
|
||||
candidate.RequiresClarification = true
|
||||
@@ -168,7 +208,7 @@ func addScenarioPlan(input work.PlanningInput, millis int64, candidate *work.Pla
|
||||
}
|
||||
|
||||
func requiresDurableWork(lower string) bool {
|
||||
return containsAny(lower, "тормозит", "медлен", "производительн", "разберись", "почему", "добавь", "поддержк", "возможность", "реализ", "письм", "почт", "отправь", "ивану", "сообщи", "установи", "большую модель")
|
||||
return containsAny(lower, "тормозит", "медлен", "производительн", "разберись", "почему", "добавь", "поддержк", "возможность", "реализ", "патч", "исправь", "установи", "большую модель")
|
||||
}
|
||||
|
||||
func addRequirement(candidate *work.PlanCandidate, kind capability.CapabilityKind, purpose string, precondition epistemics.VerificationState, millis int64) {
|
||||
@@ -257,3 +297,21 @@ func containsAny(value string, needles ...string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isReminderRequest(lower string) bool {
|
||||
return containsAny(lower, "напомни", "напомин")
|
||||
}
|
||||
|
||||
func isEmailReviewRequest(lower string) bool {
|
||||
return containsAny(lower, "посмотри новые письма", "какие нужно ответить", "на какие нужно ответить", "разбери почту")
|
||||
}
|
||||
|
||||
func isExplicitEmailGapRequest(lower string) bool {
|
||||
hasMailObject := containsAny(lower, "письмо", "письма", "почту", "почта")
|
||||
hasMailAction := containsAny(lower, "отправь", "ответить", "ответь", "напиши")
|
||||
return hasMailObject && hasMailAction
|
||||
}
|
||||
|
||||
func isUnsupportedCapabilityRequest(lower string) bool {
|
||||
return containsAny(lower, "не умеешь", "подключи интеграцию", "подключи сервис", "интеграц", "неизвестн", "нет capability", "создай adapter", "создай адаптер", "сервис, которого сейчас нет")
|
||||
}
|
||||
|
||||
@@ -92,3 +92,37 @@ func TestProductionAuditArtifactExists(t *testing.T) {
|
||||
t.Fatalf("unexpected audit facts: %+v", artifact)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSemanticDeferralMetamorphicEvidenceExists(t *testing.T) {
|
||||
payload, err := os.ReadFile("../../docs/evidence/semantic-deferral-metamorphic-evidence-2026-08-17-v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var artifact struct {
|
||||
ArtifactID string `json:"artifact_id"`
|
||||
TaskID string `json:"task_id"`
|
||||
RawModelEvidence []struct {
|
||||
Utterance string `json:"utterance"`
|
||||
Result string `json:"result"`
|
||||
} `json:"raw_model_evidence"`
|
||||
DomovoyBoundaryEvidence struct {
|
||||
BeforeFix []struct {
|
||||
Utterance string `json:"utterance"`
|
||||
SemanticPath string `json:"semantic_path"`
|
||||
CognitionRequested bool `json:"cognition_requested"`
|
||||
} `json:"before_fix"`
|
||||
} `json:"domovoy_boundary_evidence"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &artifact); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if artifact.ArtifactID != "semantic-deferral-metamorphic-evidence-2026-08-17-v1" || artifact.TaskID != "DEV-000072" {
|
||||
t.Fatalf("unexpected semantic deferral evidence identity: %+v", artifact)
|
||||
}
|
||||
if len(artifact.RawModelEvidence) < 4 {
|
||||
t.Fatalf("expected CPU and GPU raw evidence, got %+v", artifact.RawModelEvidence)
|
||||
}
|
||||
if len(artifact.DomovoyBoundaryEvidence.BeforeFix) < 2 || artifact.DomovoyBoundaryEvidence.BeforeFix[0].SemanticPath != "ASSERTION" || artifact.DomovoyBoundaryEvidence.BeforeFix[0].CognitionRequested {
|
||||
t.Fatalf("expected pre-fix Domovoy boundary evidence, got %+v", artifact.DomovoyBoundaryEvidence)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,7 +382,11 @@ func runCase(item Case, projects []ProjectDescriptor) (CaseResult, error) {
|
||||
PrincipalID: utterance.PrincipalID,
|
||||
Message: utterance.Content,
|
||||
SpeechAct: work.SpeechActRef(semantic.Interpretation.SpeechAct),
|
||||
SecondarySpeechAct: work.SpeechActRef(semantic.Interpretation.SecondarySpeechAct),
|
||||
AssertionVerification: epistemics.VerificationState(semantic.Interpretation.AssertionVerification),
|
||||
CapabilityHints: semanticCapabilityKinds(semantic.CapabilityHints),
|
||||
NeedsCognition: semantic.NeedsCognition,
|
||||
RequiresClarification: semantic.Interpretation.Clarification == interaction.ClarificationRequired,
|
||||
ReceivedAtUnixMillis: utterance.ReceivedAtUnixMillis,
|
||||
}
|
||||
if len(semantic.Projects) > 0 {
|
||||
@@ -821,3 +825,15 @@ func replyMentionsAny(reply string, markers []string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func semanticCapabilityKinds(values []string) []capability.CapabilityKind {
|
||||
result := make([]capability.CapabilityKind, 0, len(values))
|
||||
for _, value := range values {
|
||||
kind := capability.CapabilityKind(strings.TrimSpace(value))
|
||||
if kind == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, kind)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -43,11 +43,13 @@ func TestRealityEvaluationBaselineProducesInspectableSignals(t *testing.T) {
|
||||
if artifact.SolvedAutonomously == 0 || artifact.GenuineCapabilityGaps == 0 {
|
||||
t.Fatalf("expected multiple baseline buckets, got %+v", artifact)
|
||||
}
|
||||
if artifact.ConsequenceReasoningFailures == 0 {
|
||||
t.Fatalf("expected at least one consequence reasoning failure in current baseline: %+v", artifact)
|
||||
if artifact.FailedWithAvailableCapability == 0 && artifact.ConsequenceReasoningFailures == 0 && artifact.IntentFailures == 0 {
|
||||
if artifact.CorrectHumanIntervention == 0 || artifact.GenuineCapabilityGaps == 0 {
|
||||
t.Fatalf("expected inspectable routing signals even with zero current failures: %+v", artifact)
|
||||
}
|
||||
}
|
||||
if len(artifact.WorstFailures) == 0 {
|
||||
t.Fatalf("expected worst failures to be captured")
|
||||
if artifact.FailedWithAvailableCapability > 0 && len(artifact.WorstFailures) == 0 {
|
||||
t.Fatalf("expected worst failures to be captured when failures remain")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user