DEV-000280 Add local generic Agent Execution #258

Merged
m merged 2 commits from codex/dev-000248-self-development-runtime-status into main 2026-08-24 00:01:16 +00:00
6 changed files with 107 additions and 3 deletions
+39 -1
View File
@@ -12,6 +12,7 @@ import (
"git.cin.su/m/Assistant/contracts/artifact"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/work"
capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain"
)
@@ -63,6 +64,9 @@ func (b bridge) executeHandler(w http.ResponseWriter, r *http.Request) {
func (b bridge) execute(ctx context.Context, request work.AgentExecutionRequest) (work.AgentExecutionResult, error) {
now := time.Now().UnixMilli()
intent := request.Intent
if intent.TaskEvaluationExecution != nil && intent.TaskEvaluationExecution.GenericMessage != "" {
return b.executeGenericCognitiveWork(ctx, request, *intent.TaskEvaluationExecution)
}
if intent.TaskEvaluationExecution == nil || intent.TaskEvaluationProfile == nil || len(intent.TaskEvaluationCases) == 0 {
return work.AgentExecutionResult{State: work.AgentExecutionWaiting, Summary: "локальный bridge не поддерживает это намерение; работа сохранена и ожидает подходящего исполнителя", ObservedAtUnixMillis: now}, nil
}
@@ -108,6 +112,40 @@ func (b bridge) execute(ctx context.Context, request work.AgentExecutionRequest)
return work.AgentExecutionResult{State: work.AgentExecutionCompleted, Summary: "local bridge executed task evaluation and persisted verifier callback", OutcomeRef: string(evaluation.ID), ArtifactRefs: []string{string(evaluation.ArtifactID)}, ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
func (b bridge) executeGenericCognitiveWork(ctx context.Context, request work.AgentExecutionRequest, execution work.TaskEvaluationDispatchContext) (work.AgentExecutionResult, error) {
if execution.GraphID == "" || execution.NodeID == "" || execution.ActorID == "" || execution.PrincipalID == "" || execution.ScopeID == "" {
return work.AgentExecutionResult{State: work.AgentExecutionWaiting, Summary: "generic cognitive work context is incomplete; durable work remains waiting for rerouting", ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
maxOutputBytes := execution.GenericMaxOutputBytes
if maxOutputBytes < 1 {
maxOutputBytes = 4096
}
dataPolicy := capability.DataPolicy(execution.GenericDataPolicy)
if dataPolicy == "" {
dataPolicy = capability.DataPolicyLocalOnly
}
response, err := postJSON[cognitive.GatewayAdvisoryResponse](ctx, b.client, b.cognitiveURL+"/v1/advisories", cognitive.GatewayAdvisoryRequest{
Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID("agent-work:" + request.Intent.DispatchID), ActorID: execution.ActorID,
PrincipalID: execution.PrincipalID, ScopeID: execution.ScopeID, Message: execution.GenericMessage, Locale: execution.GenericLocale,
MaxOutputBytes: maxOutputBytes, DataPolicy: dataPolicy, Meaning: cognitive.TaskMeaning{SpeechAct: "DIRECTIVE", RequestedOutcome: execution.GenericPurpose},
})
if err != nil {
return work.AgentExecutionResult{}, err
}
if response.FinishReason != cognitive.FinishCompleted || strings.TrimSpace(response.AdvisoryText) == "" {
return work.AgentExecutionResult{}, fmt.Errorf("generic cognitive work did not complete: %s", response.FinishReason)
}
candidate := work.GraphResultCallback{GraphID: execution.GraphID, Revision: execution.GraphRevision, Candidate: work.ResultCandidate{
ID: "candidate:" + request.Intent.DispatchID, GraphID: execution.GraphID, GraphRevision: execution.GraphRevision, NodeID: execution.NodeID,
AuthorRunID: request.Intent.RunID, ArtifactRefs: []work.EvidenceRef{work.EvidenceRef("agent-output:" + request.Intent.DispatchID)}, EvidenceSummary: "cognitive gateway output recorded as an unverified candidate",
Summary: response.AdvisoryText,
}}
if _, err := postJSON[json.RawMessage](ctx, b.client, b.callbackURL+"/v1/work-graphs/result-callback", candidate); err != nil {
return work.AgentExecutionResult{}, err
}
return work.AgentExecutionResult{State: work.AgentExecutionCompleted, Summary: "local cognitive specialist produced a candidate for independent verification", OutcomeRef: "candidate:" + request.Intent.DispatchID, ArtifactRefs: []string{"agent-output:" + request.Intent.DispatchID}, ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
func hasCriticalFailure(observations []capability.TaskCaseObservation) bool {
for _, observation := range observations {
if observation.CriticalFailure {
@@ -151,7 +189,7 @@ func healthHandler(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"runtime":"agent-execution-bridge","status":"ok","capabilities":["task-evaluation"]}`))
_, _ = w.Write([]byte(`{"runtime":"agent-execution-bridge","status":"ok","capabilities":["task-evaluation","generic-cognitive-work"]}`))
}
func envOr(name, fallback string) string {
+31
View File
@@ -8,6 +8,7 @@ import (
"testing"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/work"
)
@@ -53,3 +54,33 @@ func TestBridgeRunsTaskEvaluationThroughVerifierAndCallback(t *testing.T) {
t.Fatalf("result=%+v err=%v", result, err)
}
}
func TestBridgeRunsGenericCognitiveWorkAsUnverifiedGraphCandidate(t *testing.T) {
var callback work.GraphResultCallback
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/v1/advisories":
var request cognitive.GatewayAdvisoryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil || request.Message != "find the bounded solution" {
t.Fatalf("unexpected advisory request: %+v err=%v", request, err)
}
_ = json.NewEncoder(w).Encode(cognitive.GatewayAdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: request.RequestID, AdvisoryText: "candidate answer", FinishReason: cognitive.FinishCompleted})
case "/v1/work-graphs/result-callback":
if err := json.NewDecoder(r.Body).Decode(&callback); err != nil {
t.Fatal(err)
}
w.WriteHeader(http.StatusNoContent)
default:
http.NotFound(w, r)
}
}))
defer server.Close()
b := bridge{cognitiveURL: server.URL, callbackURL: server.URL, client: server.Client()}
result, err := b.execute(context.Background(), work.AgentExecutionRequest{Intent: work.AgentDispatchIntent{
DispatchID: "dispatch-generic", RunID: "run-generic",
TaskEvaluationExecution: &work.TaskEvaluationDispatchContext{GraphID: "graph-1", GraphRevision: 2, NodeID: "node-1", ActorID: "actor-1", PrincipalID: "principal-1", ScopeID: "scope-1", GenericMessage: "find the bounded solution", GenericPurpose: "investigate", GenericDataPolicy: string(capability.DataPolicyLocalOnly)},
}})
if err != nil || result.State != work.AgentExecutionCompleted || callback.Candidate.Summary != "candidate answer" || callback.Candidate.AuthorRunID != "run-generic" {
t.Fatalf("result=%+v callback=%+v err=%v", result, callback, err)
}
}
+1
View File
@@ -102,6 +102,7 @@ func workerStatusHandler(executorConfigured bool, localFallback ...interface{})
baseStatus.Mode = "execution"
baseStatus.Ready = true
baseStatus.Reason = "typed agent execution bridge configured"
baseStatus.Capabilities = []string{"task-evaluation", "generic-cognitive-work"}
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet || (r.URL.Path != "/healthz" && r.URL.Path != "/readyz" && r.URL.Path != "/status") {
+8
View File
@@ -83,4 +83,12 @@ type TaskEvaluationDispatchContext struct {
ActorID identity.ActorID `json:"actor_id"`
PrincipalID identity.PrincipalID `json:"principal_id"`
ScopeID authority.ScopeID `json:"scope_id"`
// GenericMessage turns the existing durable dispatch context into a
// discriminated local cognitive-work payload without adding a second
// persistence column. It is ignored by task-evaluation executors.
GenericMessage string `json:"generic_message,omitempty"`
GenericPurpose string `json:"generic_purpose,omitempty"`
GenericLocale string `json:"generic_locale,omitempty"`
GenericDataPolicy string `json:"generic_data_policy,omitempty"`
GenericMaxOutputBytes int `json:"generic_max_output_bytes,omitempty"`
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000279-live-audit-matrix-reconciliation.json",
"task_id": "DEV-000279",
"task_file": "dev/tasks/DEV-000280-local-generic-agent-execution.json",
"task_id": "DEV-000280",
"task_revision": 1
}
@@ -0,0 +1,26 @@
{
"task_id": "DEV-000280",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "51db637",
"risk_tier": "E",
"intent": "Add a typed local generic cognitive specialist execution path for Work Graph nodes, preserving independent verification and durable recovery semantics.",
"requirement_ids": ["generic local agent execution", "candidate output is not verified fact", "work graph callback remains independently verifiable"],
"allowed_change_scope": {
"modules": ["work"],
"paths": ["contracts/work/bridge.go", "contracts/work/lease.go", "adapters/postgres/work_repository.go", "cmd/agent-execution-bridge/**", "cmd/agent-worker/**", "tests/agent_execution/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000280-local-generic-agent-execution.json"]
},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "modules/identity/**", "modules/authority/**", "LLM-local/**", "deploy/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["contracts/work@v1", "cognitive gateway advisory endpoint", "work graph result callback"], "may_change": ["contracts/work@v1 bridge extension"]},
"semantic_owner": "work",
"canonical_state": ["work dispatch intent and graph candidate only; no canonical fact or external mutation"],
"state_transitions": ["queued -> leased -> executing -> candidate recorded -> verifying", "unsupported or unavailable -> WAITING with durable retry"],
"side_effects_and_privileges": ["local cognitive gateway call", "work graph candidate callback", "no external mutation"],
"persistence_and_migrations": ["reuse existing work.agent_dispatch_intents and graph candidate persistence; no migration"],
"failure_retry_restart": ["bridge timeout or gateway failure returns error for durable retry", "unsupported context returns WAITING", "lease heartbeat and stale claim recovery remain unchanged"],
"assumptions": ["Cognitive Gateway is the only model boundary", "generic output is an unverified candidate until independent synthesis", "local CPU/Lifeboat fallback remains the default capability"],
"acceptance_scenarios": ["typed generic request reaches gateway and callback", "callback candidate leaves graph eligible for verification rather than success", "missing generic context returns WAITING", "gateway failure is retryable and never reported completed"],
"required_tests": ["go test ./...", "go vet ./...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_ARCHITECTURE_GAP"]
}