DEV-000069 general agent delegation codex bridge foundation
CI / test (push) Successful in 20s
CI / test (pull_request) Successful in 20s

This commit is contained in:
m
2026-08-17 20:07:14 +03:00
parent d870e1361d
commit b3491520bd
21 changed files with 1914 additions and 20 deletions
+64 -16
View File
@@ -162,10 +162,10 @@ func (r *ContinuityRepository) ClaimDueReminder(ctx context.Context, id continui
return continuity.ProspectiveItem{}, continuity.AttentionItem{}, err
}
err = tx.QueryRow(ctx, `INSERT INTO continuity.attention_items (
attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,level,state,summary,
attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,
due_at_unix_millis,created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,
action_attempt_id,delivery_outcome,response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,0,'','','','','','','','',0,1)
VALUES ($1,$2,$3,$4,$5,'',$6,$7,$8,$9,$10,0,'','','','','','','','',0,1)
ON CONFLICT (source_kind, source_prospective_item_id) DO UPDATE SET
state=EXCLUDED.state,
due_at_unix_millis=EXCLUDED.due_at_unix_millis,
@@ -181,12 +181,12 @@ func (r *ContinuityRepository) ClaimDueReminder(ctx context.Context, id continui
response_outcome='',
responded_at_unix_millis=0,
revision=continuity.attention_items.revision+1
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,level,state,summary,due_at_unix_millis,
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,due_at_unix_millis,
created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,action_attempt_id,delivery_outcome,
response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision`,
attention.ID, attention.OwnerPrincipalID, attention.ScopeID, attention.SourceKind, attention.SourceProspectiveID,
attention.Level, attention.State, attention.Summary, attention.DueAtUnixMillis, attention.CreatedAtUnixMillis).Scan(
&attention.ID, &attention.OwnerPrincipalID, &attention.ScopeID, &attention.SourceKind, &attention.SourceProspectiveID,
&attention.ID, &attention.OwnerPrincipalID, &attention.ScopeID, &attention.SourceKind, &attention.SourceProspectiveID, &attention.SourceExternalRef,
&attention.Level, &attention.State, &attention.Summary, &attention.DueAtUnixMillis, &attention.CreatedAtUnixMillis,
&attention.DeliveredAtUnixMillis, &attention.ActionIntentID, &attention.AuthorizedActionID, &attention.ActionAttemptID,
&attention.DeliveryOutcome, &attention.ResponseID, &attention.ResponseUtteranceID, &attention.ResponseOperation,
@@ -204,7 +204,7 @@ func (r *ContinuityRepository) PendingAttention(ctx context.Context, limit int)
if limit < 1 || limit > 100 {
return nil, fmt.Errorf("invalid attention limit")
}
rows, err := r.pool.Query(ctx, `SELECT attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,level,state,summary,
rows, err := r.pool.Query(ctx, `SELECT attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,
due_at_unix_millis,created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,action_attempt_id,delivery_outcome,
response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision
FROM continuity.attention_items WHERE state=$1 ORDER BY due_at_unix_millis, attention_item_id LIMIT $2`, continuity.AttentionPending, limit)
@@ -219,7 +219,7 @@ func (r *ContinuityRepository) AttentionByOwner(ctx context.Context, owner ident
if owner == "" || limit < 1 || limit > 100 {
return nil, fmt.Errorf("invalid attention owner query")
}
rows, err := r.pool.Query(ctx, `SELECT attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,level,state,summary,
rows, err := r.pool.Query(ctx, `SELECT attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,
due_at_unix_millis,created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,action_attempt_id,delivery_outcome,
response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision
FROM continuity.attention_items WHERE owner_principal_id=$1 ORDER BY due_at_unix_millis DESC, attention_item_id DESC LIMIT $2`, owner, limit)
@@ -235,7 +235,7 @@ func (r *ContinuityRepository) ResponseCandidates(ctx context.Context, owner ide
return nil, fmt.Errorf("invalid response candidate query")
}
rows, err := r.pool.Query(ctx, `SELECT
COALESCE(a.attention_item_id,''),COALESCE(a.owner_principal_id,''),COALESCE(a.scope_id,''),COALESCE(a.source_kind,''),COALESCE(a.source_prospective_item_id,''),COALESCE(a.level,''),COALESCE(a.state,''),COALESCE(a.summary,''),
COALESCE(a.attention_item_id,''),COALESCE(a.owner_principal_id,''),COALESCE(a.scope_id,''),COALESCE(a.source_kind,''),COALESCE(a.source_prospective_item_id,''),COALESCE(a.source_external_ref,''),COALESCE(a.level,''),COALESCE(a.state,''),COALESCE(a.summary,''),
COALESCE(a.due_at_unix_millis,0),COALESCE(a.created_at_unix_millis,0),COALESCE(a.delivered_at_unix_millis,0),COALESCE(a.action_intent_id,''),COALESCE(a.authorized_action_id,''),COALESCE(a.action_attempt_id,''),COALESCE(a.delivery_outcome,''),
COALESCE(a.response_id,''),COALESCE(a.response_utterance_id,''),COALESCE(a.response_operation,''),COALESCE(a.response_outcome,''),COALESCE(a.responded_at_unix_millis,0),COALESCE(a.revision,0),
p.prospective_item_id,p.commitment_id,p.owner_principal_id,p.scope_id,p.goal_id,p.source_intent_id,p.source_utterance_id,p.summary,p.trigger_at_unix_millis,p.state,p.revision
@@ -255,12 +255,12 @@ func (r *ContinuityRepository) ResponseCandidates(ctx context.Context, owner ide
var candidates []continuitydomain.ResponseCandidate
for rows.Next() {
var candidate continuitydomain.ResponseCandidate
var attentionID, attentionOwner, attentionScope, sourceKind, sourceProspective, level, attentionState, attentionSummary string
var attentionID, attentionOwner, attentionScope, sourceKind, sourceProspective, sourceExternalRef, level, attentionState, attentionSummary string
var actionIntent, authorizedAction, actionAttempt, deliveryOutcome, responseID, responseUtterance, responseOperation, responseOutcome string
var dueAt, createdAt, deliveredAt, respondedAt int64
var attentionRevision uint64
if err := rows.Scan(
&attentionID, &attentionOwner, &attentionScope, &sourceKind, &sourceProspective, &level, &attentionState, &attentionSummary,
&attentionID, &attentionOwner, &attentionScope, &sourceKind, &sourceProspective, &sourceExternalRef, &level, &attentionState, &attentionSummary,
&dueAt, &createdAt, &deliveredAt, &actionIntent, &authorizedAction, &actionAttempt, &deliveryOutcome,
&responseID, &responseUtterance, &responseOperation, &responseOutcome, &respondedAt, &attentionRevision,
&candidate.Prospective.ID, &candidate.Prospective.CommitmentID, &candidate.Prospective.OwnerPrincipalID,
@@ -276,6 +276,7 @@ func (r *ContinuityRepository) ResponseCandidates(ctx context.Context, owner ide
ScopeID: authority.ScopeID(attentionScope),
SourceKind: continuity.AttentionSourceKind(sourceKind),
SourceProspectiveID: continuity.ProspectiveItemID(sourceProspective),
SourceExternalRef: sourceExternalRef,
Level: continuity.AttentionLevel(level),
State: continuity.AttentionState(attentionState),
Summary: attentionSummary,
@@ -378,19 +379,66 @@ func (r *ContinuityRepository) ApplyHumanResponse(ctx context.Context, response
if err = markAttentionResponded(ctx, tx, response, continuity.AttentionResolved); err != nil {
return err
}
case continuity.ResponseContinue, continuity.ResponseReject:
if response.TargetAttentionID == "" {
return ErrAttentionNotFound
}
if err = markAttentionResponded(ctx, tx, response, continuity.AttentionResolved); err != nil {
return err
}
}
return tx.Commit(ctx)
}
func (r *ContinuityRepository) CreateAttentionItem(ctx context.Context, item continuity.AttentionItem) (continuity.AttentionItem, error) {
err := r.pool.QueryRow(ctx, `INSERT INTO continuity.attention_items(
attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,
due_at_unix_millis,created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,action_attempt_id,delivery_outcome,
response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22)
ON CONFLICT (source_kind, source_external_ref) WHERE source_external_ref <> '' DO UPDATE SET
level=EXCLUDED.level,
state=EXCLUDED.state,
summary=EXCLUDED.summary,
due_at_unix_millis=EXCLUDED.due_at_unix_millis,
created_at_unix_millis=EXCLUDED.created_at_unix_millis,
delivered_at_unix_millis=0,
action_intent_id='',
authorized_action_id='',
action_attempt_id='',
delivery_outcome='',
response_id='',
response_utterance_id='',
response_operation='',
response_outcome='',
responded_at_unix_millis=0,
revision=continuity.attention_items.revision+1
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,due_at_unix_millis,
created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,action_attempt_id,delivery_outcome,
response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision`,
item.ID, item.OwnerPrincipalID, item.ScopeID, item.SourceKind, item.SourceProspectiveID, item.SourceExternalRef, item.Level, item.State,
item.Summary, item.DueAtUnixMillis, item.CreatedAtUnixMillis, item.DeliveredAtUnixMillis, item.ActionIntentID, item.AuthorizedActionID,
item.ActionAttemptID, item.DeliveryOutcome, item.ResponseID, item.ResponseUtteranceID, item.ResponseOperation, item.ResponseOutcome,
item.RespondedAtUnixMillis, item.Revision).Scan(
&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID, &item.SourceExternalRef, &item.Level, &item.State,
&item.Summary, &item.DueAtUnixMillis, &item.CreatedAtUnixMillis, &item.DeliveredAtUnixMillis, &item.ActionIntentID, &item.AuthorizedActionID,
&item.ActionAttemptID, &item.DeliveryOutcome, &item.ResponseID, &item.ResponseUtteranceID, &item.ResponseOperation, &item.ResponseOutcome,
&item.RespondedAtUnixMillis, &item.Revision)
if err != nil {
return continuity.AttentionItem{}, fmt.Errorf("create attention item: %w", err)
}
return item, nil
}
func (r *ContinuityRepository) MarkAttentionDelivering(ctx context.Context, id continuity.AttentionItemID, revision continuity.AttentionRevision) (continuity.AttentionItem, error) {
var item continuity.AttentionItem
err := r.pool.QueryRow(ctx, `UPDATE continuity.attention_items SET state=$1, revision=revision+1
WHERE attention_item_id=$2 AND state=$3 AND revision=$4
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,level,state,summary,due_at_unix_millis,
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,due_at_unix_millis,
created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,action_attempt_id,delivery_outcome,
response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision`,
continuity.AttentionDelivering, id, continuity.AttentionPending, revision).Scan(
&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID, &item.Level, &item.State,
&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID, &item.SourceExternalRef, &item.Level, &item.State,
&item.Summary, &item.DueAtUnixMillis, &item.CreatedAtUnixMillis, &item.DeliveredAtUnixMillis, &item.ActionIntentID,
&item.AuthorizedActionID, &item.ActionAttemptID, &item.DeliveryOutcome, &item.ResponseID, &item.ResponseUtteranceID,
&item.ResponseOperation, &item.ResponseOutcome, &item.RespondedAtUnixMillis, &item.Revision)
@@ -406,12 +454,12 @@ func (r *ContinuityRepository) MarkAttentionDelivered(ctx context.Context, id co
SET state=$1, revision=revision+1, delivered_at_unix_millis=$2, action_intent_id=$3, authorized_action_id=$4,
action_attempt_id=$5, delivery_outcome=$6
WHERE attention_item_id=$7 AND state=$8 AND revision=$9
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,level,state,summary,due_at_unix_millis,
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,due_at_unix_millis,
created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,action_attempt_id,delivery_outcome,
response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision`,
continuity.AttentionDelivered, deliveredAtUnixMillis, intentID, authorizedID, attemptID, outcome,
id, continuity.AttentionDelivering, revision).Scan(
&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID, &item.Level, &item.State,
&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID, &item.SourceExternalRef, &item.Level, &item.State,
&item.Summary, &item.DueAtUnixMillis, &item.CreatedAtUnixMillis, &item.DeliveredAtUnixMillis, &item.ActionIntentID,
&item.AuthorizedActionID, &item.ActionAttemptID, &item.DeliveryOutcome, &item.ResponseID, &item.ResponseUtteranceID,
&item.ResponseOperation, &item.ResponseOutcome, &item.RespondedAtUnixMillis, &item.Revision)
@@ -427,12 +475,12 @@ func (r *ContinuityRepository) MarkAttentionFailed(ctx context.Context, id conti
SET state=$1, revision=revision+1, action_intent_id=$2, authorized_action_id=$3,
action_attempt_id=$4, delivery_outcome=$5
WHERE attention_item_id=$6 AND state=$7 AND revision=$8
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,level,state,summary,due_at_unix_millis,
RETURNING attention_item_id,owner_principal_id,scope_id,source_kind,source_prospective_item_id,source_external_ref,level,state,summary,due_at_unix_millis,
created_at_unix_millis,delivered_at_unix_millis,action_intent_id,authorized_action_id,action_attempt_id,delivery_outcome,
response_id,response_utterance_id,response_operation,response_outcome,responded_at_unix_millis,revision`,
continuity.AttentionFailed, intentID, authorizedID, attemptID, outcome,
id, continuity.AttentionDelivering, revision).Scan(
&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID, &item.Level, &item.State,
&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID, &item.SourceExternalRef, &item.Level, &item.State,
&item.Summary, &item.DueAtUnixMillis, &item.CreatedAtUnixMillis, &item.DeliveredAtUnixMillis, &item.ActionIntentID,
&item.AuthorizedActionID, &item.ActionAttemptID, &item.DeliveryOutcome, &item.ResponseID, &item.ResponseUtteranceID,
&item.ResponseOperation, &item.ResponseOutcome, &item.RespondedAtUnixMillis, &item.Revision)
@@ -541,7 +589,7 @@ func scanAttentionRows(rows continuityRows) ([]continuity.AttentionItem, error)
items := make([]continuity.AttentionItem, 0)
for rows.Next() {
var item continuity.AttentionItem
if err := rows.Scan(&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID,
if err := rows.Scan(&item.ID, &item.OwnerPrincipalID, &item.ScopeID, &item.SourceKind, &item.SourceProspectiveID, &item.SourceExternalRef,
&item.Level, &item.State, &item.Summary, &item.DueAtUnixMillis, &item.CreatedAtUnixMillis,
&item.DeliveredAtUnixMillis, &item.ActionIntentID, &item.AuthorizedActionID, &item.ActionAttemptID,
&item.DeliveryOutcome, &item.ResponseID, &item.ResponseUtteranceID, &item.ResponseOperation,
+218
View File
@@ -74,3 +74,221 @@ func (r *WorkRepository) SavePlanningResult(ctx context.Context, result work.Pla
}
return tx.Commit(ctx)
}
func (r *WorkRepository) SaveDelegationBundle(ctx context.Context, delegation work.Delegation, run work.AgentRun) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
if _, err = tx.Exec(ctx, `INSERT INTO work.delegations(
delegation_id,goal_id,work_plan_id,plan_candidate_id,capability_requirement_id,capability_kind,
selected_implementation_id,selected_implementation_revision,selected_implementation_kind,protocol,
source_intent_id,source_utterance_id,conversation_id,project_id,project_display_name,desired_outcome,
constraint_summary,verification_summary,context_summary,context_refs,state,latest_run_id,latest_result_id,
created_at_unix_millis,updated_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25)
ON CONFLICT(delegation_id) DO NOTHING`,
delegation.ID, delegation.GoalID, delegation.PlanID, delegation.PlanCandidateID, delegation.CapabilityRequirementID,
delegation.CapabilityKind, delegation.SelectedImplementationID, delegation.SelectedImplementationRev,
delegation.SelectedImplementationKind, delegation.Protocol, delegation.SourceIntentID, delegation.SourceUtteranceID,
delegation.ConversationID, delegation.ProjectID, delegation.ProjectDisplayName, delegation.DesiredOutcome,
delegation.ConstraintSummary, delegation.VerificationSummary, delegation.ContextSummary, delegation.ContextRefs,
delegation.State, delegation.LatestRunID, delegation.LatestResultID, delegation.CreatedAtUnixMillis, delegation.UpdatedAtUnixMillis); err != nil {
return err
}
if _, err = tx.Exec(ctx, `INSERT INTO work.agent_runs(
run_id,delegation_id,implementation_id,implementation_revision,protocol,state,derived_instruction_summary,
started_at_unix_millis,updated_at_unix_millis,completed_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT(run_id) DO NOTHING`,
run.ID, run.DelegationID, run.ImplementationID, run.ImplementationRevision, run.Protocol, run.State,
run.DerivedInstructionSummary, run.StartedAtUnixMillis, run.UpdatedAtUnixMillis, run.CompletedAtUnixMillis); err != nil {
return err
}
return tx.Commit(ctx)
}
func (r *WorkRepository) SaveAgentCheckpoint(ctx context.Context, run work.AgentRun, checkpoint work.AgentCheckpoint) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
if _, err = tx.Exec(ctx, `UPDATE work.agent_runs
SET state=$2,updated_at_unix_millis=$3,completed_at_unix_millis=$4
WHERE run_id=$1`, run.ID, run.State, run.UpdatedAtUnixMillis, run.CompletedAtUnixMillis); err != nil {
return err
}
if _, err = tx.Exec(ctx, `UPDATE work.delegations
SET state=$2,updated_at_unix_millis=$3,latest_run_id=$4
WHERE delegation_id=$1`, checkpoint.DelegationID, delegationStateForRun(run.State), checkpoint.RecordedAtUnixMillis, run.ID); err != nil {
return err
}
if _, err = tx.Exec(ctx, `INSERT INTO work.agent_checkpoints(
checkpoint_id,delegation_id,run_id,state,blocker_class,completed_summary,remaining_summary,test_summary,
branch_name,commit_hash,next_step,artifact_refs,recorded_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) ON CONFLICT(checkpoint_id) DO NOTHING`,
checkpoint.ID, checkpoint.DelegationID, checkpoint.RunID, checkpoint.State, checkpoint.BlockerClass,
checkpoint.CompletedSummary, checkpoint.RemainingSummary, checkpoint.TestSummary, checkpoint.BranchName,
checkpoint.CommitHash, checkpoint.NextStep, checkpoint.ArtifactRefs, checkpoint.RecordedAtUnixMillis); err != nil {
return err
}
return tx.Commit(ctx)
}
func (r *WorkRepository) SaveAgentResult(ctx context.Context, run work.AgentRun, result work.AgentResult) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
if _, err = tx.Exec(ctx, `UPDATE work.agent_runs
SET state=$2,updated_at_unix_millis=$3,completed_at_unix_millis=$4
WHERE run_id=$1`, run.ID, run.State, run.UpdatedAtUnixMillis, run.CompletedAtUnixMillis); err != nil {
return err
}
if _, err = tx.Exec(ctx, `UPDATE work.delegations
SET state=$2,updated_at_unix_millis=$3,latest_run_id=$4,latest_result_id=$5
WHERE delegation_id=$1`, result.DelegationID, work.DelegationVerifying, result.RecordedAtUnixMillis, run.ID, result.ID); err != nil {
return err
}
if _, err = tx.Exec(ctx, `INSERT INTO work.agent_results(
result_id,delegation_id,run_id,state,summary,outcome_ref,verification_required,verification_summary,artifact_refs,recorded_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT(result_id) DO NOTHING`,
result.ID, result.DelegationID, result.RunID, result.State, result.Summary, result.OutcomeRef, result.VerificationRequired,
result.VerificationSummary, result.ArtifactRefs, result.RecordedAtUnixMillis); err != nil {
return err
}
return tx.Commit(ctx)
}
func (r *WorkRepository) SaveResumedRun(ctx context.Context, delegation work.Delegation, run work.AgentRun) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
if _, err = tx.Exec(ctx, `UPDATE work.delegations
SET state=$2,updated_at_unix_millis=$3,latest_run_id=$4
WHERE delegation_id=$1`, delegation.ID, delegation.State, delegation.UpdatedAtUnixMillis, delegation.LatestRunID); err != nil {
return err
}
if _, err = tx.Exec(ctx, `INSERT INTO work.agent_runs(
run_id,delegation_id,implementation_id,implementation_revision,protocol,state,derived_instruction_summary,
started_at_unix_millis,updated_at_unix_millis,completed_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT(run_id) DO NOTHING`,
run.ID, run.DelegationID, run.ImplementationID, run.ImplementationRevision, run.Protocol, run.State,
run.DerivedInstructionSummary, run.StartedAtUnixMillis, run.UpdatedAtUnixMillis, run.CompletedAtUnixMillis); err != nil {
return err
}
return tx.Commit(ctx)
}
func (r *WorkRepository) SaveDelegationVerification(ctx context.Context, delegation work.Delegation, verification work.DelegationVerification) error {
tx, err := r.pool.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
if _, err = tx.Exec(ctx, `UPDATE work.delegations
SET state=$2,updated_at_unix_millis=$3
WHERE delegation_id=$1`, delegation.ID, delegation.State, delegation.UpdatedAtUnixMillis); err != nil {
return err
}
if _, err = tx.Exec(ctx, `INSERT INTO work.delegation_verifications(
verification_id,delegation_id,run_id,result_id,verified,summary,evidence_refs,recorded_at_unix_millis)
VALUES($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT(verification_id) DO NOTHING`,
verification.ID, verification.DelegationID, verification.RunID, verification.ResultID, verification.Verified,
verification.Summary, verification.EvidenceRefs, verification.RecordedAtUnixMillis); err != nil {
return err
}
return tx.Commit(ctx)
}
func (r *WorkRepository) LatestAgentResult(ctx context.Context, delegationID work.DelegationID) (work.AgentResult, error) {
var value work.AgentResult
err := r.pool.QueryRow(ctx, `SELECT result_id,delegation_id,run_id,state,summary,outcome_ref,verification_required,verification_summary,artifact_refs,recorded_at_unix_millis
FROM work.agent_results WHERE delegation_id=$1 ORDER BY recorded_at_unix_millis DESC, result_id DESC LIMIT 1`, delegationID).
Scan(&value.ID, &value.DelegationID, &value.RunID, &value.State, &value.Summary, &value.OutcomeRef, &value.VerificationRequired, &value.VerificationSummary, &value.ArtifactRefs, &value.RecordedAtUnixMillis)
if err != nil {
return work.AgentResult{}, fmt.Errorf("load latest agent result: %w", err)
}
return value, nil
}
func (r *WorkRepository) CountAgentRuns(ctx context.Context, delegationID work.DelegationID) (int, error) {
var count int
err := r.pool.QueryRow(ctx, `SELECT COUNT(*) FROM work.agent_runs WHERE delegation_id=$1`, delegationID).Scan(&count)
if err != nil {
return 0, fmt.Errorf("count agent runs: %w", err)
}
return count, nil
}
func (r *WorkRepository) LatestAgentCheckpoint(ctx context.Context, delegationID work.DelegationID) (work.AgentCheckpoint, error) {
var value work.AgentCheckpoint
err := r.pool.QueryRow(ctx, `SELECT checkpoint_id,delegation_id,run_id,state,blocker_class,completed_summary,remaining_summary,test_summary,branch_name,commit_hash,next_step,artifact_refs,recorded_at_unix_millis
FROM work.agent_checkpoints WHERE delegation_id=$1 ORDER BY recorded_at_unix_millis DESC, checkpoint_id DESC LIMIT 1`, delegationID).
Scan(&value.ID, &value.DelegationID, &value.RunID, &value.State, &value.BlockerClass, &value.CompletedSummary, &value.RemainingSummary,
&value.TestSummary, &value.BranchName, &value.CommitHash, &value.NextStep, &value.ArtifactRefs, &value.RecordedAtUnixMillis)
if err != nil {
return work.AgentCheckpoint{}, err
}
return value, nil
}
func (r *WorkRepository) LoadDelegation(ctx context.Context, id work.DelegationID) (work.Delegation, error) {
var value work.Delegation
err := r.pool.QueryRow(ctx, `SELECT delegation_id,goal_id,work_plan_id,plan_candidate_id,capability_requirement_id,capability_kind,
selected_implementation_id,selected_implementation_revision,selected_implementation_kind,protocol,
source_intent_id,source_utterance_id,conversation_id,project_id,project_display_name,desired_outcome,constraint_summary,
verification_summary,context_summary,context_refs,state,latest_run_id,latest_result_id,created_at_unix_millis,updated_at_unix_millis
FROM work.delegations WHERE delegation_id=$1`, id).
Scan(&value.ID, &value.GoalID, &value.PlanID, &value.PlanCandidateID, &value.CapabilityRequirementID, &value.CapabilityKind,
&value.SelectedImplementationID, &value.SelectedImplementationRev, &value.SelectedImplementationKind, &value.Protocol,
&value.SourceIntentID, &value.SourceUtteranceID, &value.ConversationID, &value.ProjectID, &value.ProjectDisplayName,
&value.DesiredOutcome, &value.ConstraintSummary, &value.VerificationSummary, &value.ContextSummary, &value.ContextRefs,
&value.State, &value.LatestRunID, &value.LatestResultID, &value.CreatedAtUnixMillis, &value.UpdatedAtUnixMillis)
if err != nil {
return work.Delegation{}, fmt.Errorf("load delegation: %w", err)
}
return value, nil
}
func (r *WorkRepository) LoadAgentRun(ctx context.Context, id work.AgentRunID) (work.AgentRun, error) {
var value work.AgentRun
err := r.pool.QueryRow(ctx, `SELECT run_id,delegation_id,implementation_id,implementation_revision,protocol,state,derived_instruction_summary,started_at_unix_millis,updated_at_unix_millis,completed_at_unix_millis
FROM work.agent_runs WHERE run_id=$1`, id).
Scan(&value.ID, &value.DelegationID, &value.ImplementationID, &value.ImplementationRevision, &value.Protocol, &value.State, &value.DerivedInstructionSummary,
&value.StartedAtUnixMillis, &value.UpdatedAtUnixMillis, &value.CompletedAtUnixMillis)
if err != nil {
return work.AgentRun{}, fmt.Errorf("load agent run: %w", err)
}
return value, nil
}
func delegationStateForRun(state work.AgentRunState) work.DelegationState {
switch state {
case work.AgentRunStarted, work.AgentRunRunning:
return work.DelegationRunning
case work.AgentRunWaiting, work.AgentRunCheckpointed, work.AgentRunExecutionWindowExhausted:
return work.DelegationWaiting
case work.AgentRunBlocked:
return work.DelegationBlocked
case work.AgentRunCompleted:
return work.DelegationVerifying
case work.AgentRunCancelled:
return work.DelegationCancelled
case work.AgentRunFailed:
return work.DelegationFailed
default:
return work.DelegationPending
}
}
func latestRowErr(err error) error {
if err == nil {
return nil
}
return err
}
@@ -0,0 +1,54 @@
# ADR-0017 — General Agent Delegation and Codex Bridge V1
Status: Accepted
Task: DEV-000069
## Context
Domovoy already has Human Interaction, Situation/context, optional durable Work/Plan, Capability registry/resolution, Continuity/Attention, HumanResponse, Experience/Reflection, Skill evolution, Secret Resolver and Cognitive Gateway/Router. The next step is not "make Codex the center", but to let Domovoy delegate suitable work to a general external intelligent agent while preserving canonical human intent, work semantics, authority boundaries and future portability to non-Codex agents.
The first real implementation is Codex for software-engineering work. However, a model is not an external agent. Cognitive models provide advice/candidates, while an external agent may inspect state, use tools, modify files, run tests, maintain a long-running task, checkpoint, resume and return evidence-bearing results.
## Decision
1. Do not create a new canonical owner for DEV-000069.
2. `capability` remains the canonical owner for agent implementation registration by reusing `CapabilityImplementation` with `Kind=AGENT`. A Codex bridge is one concrete implementation, not a new canonical capability identity.
3. `work` remains the canonical owner for durable delegation lifecycle. DEV-000069 extends Work semantics from planning-only `DelegationCandidate` to real delegation execution state such as `Delegation`, `AgentRun`, `AgentCheckpoint` and `AgentResult`.
4. `continuity` remains the canonical owner for human waiting/attention semantics. Human-dependent blockers or completed long-running delegations surface through Attention rather than a Codex-specific notification path.
5. `epistemics` remains the canonical owner for delegation outcome evidence through `ExperienceRecord` and later reflection, using typed refs to Work, Capability, Artifact and Interaction state.
6. `artifact` remains the place for durable artifact metadata/refs when an agent run produces file, diff, test, log, PR or deployment evidence. Work stores typed refs, not payload copies.
7. `OriginalHumanIntent`, `Goal`, `Work`, `CapabilityRequirement` and `Delegation` must remain canonical semantic records. Agent-specific instruction or prompt text is only a derived adapter artifact with provenance that distinguishes:
- what the human asked;
- what Domovoy inferred;
- what project/runtime context was added;
- how completion will be verified.
8. Execution-window exhaustion is a run lifecycle state, not automatic task failure. The same higher-level delegation may continue through later runs while preserving checkpoint/history.
9. Codex transport/UI wording is adapter-local. Canonical state must stay valid if the coding agent is replaced by another future agent.
## Ownership
- `interaction` owns the append-only human request/correction history and original intent traceability.
- `situation` resolves project/repository context and active focus used for delegation context compilation.
- `work` owns durable delegation requests, selected implementation refs, run lifecycle, checkpoint/result refs and verification gating before Work completion.
- `capability` owns executor-neutral capability identity and concrete eligible implementations, including `AGENT` implementations such as Codex.
- `continuity` owns waiting/attention/human-response continuation for delegation progress and genuine human blockers.
- `epistemics` owns experience/reflection evidence derived from delegation outcomes.
- `artifact` owns durable metadata for diffs/logs/checkpoints/results when they must survive outside a transient adapter response.
## Boundaries
- `SOFTWARE_ENGINEERING`, `MODIFY_CODE`, `INSPECT_REPOSITORY` and `RUN_TESTS` remain executor-neutral capability semantics.
- `MODIFY_CODE != CODEX`.
- Canonical Work/Delegation does not become a Codex prompt.
- Secret access remains least-privilege through CredentialRef/Secret Resolver. Agent capability availability does not imply credential access or authority.
- A completed agent run does not automatically mark higher-level Work complete. Verification must inspect available evidence.
- Technical failures, execution-window exhaustion and human-dependent blockers are distinct lifecycle states.
- `LLM-free` startup must continue to pass when Codex is unavailable.
## Consequences
- Domovoy can delegate suitable software-engineering work without forcing the human to author a giant Codex prompt.
- The first production bridge can target Codex while leaving canonical semantics portable to future scientific, accounting, research or alternate coding agents.
- Long-running agent work can checkpoint and continue across bounded execution windows.
- Human chat remains simple, while operator/debug tooling can inspect the durable delegation/run/checkpoint/result state.
+60
View File
@@ -12,6 +12,7 @@ import (
type attentionRepository interface {
AttentionByOwner(context.Context, identity.PrincipalID, int) ([]continuity.AttentionItem, error)
CreateAttentionItem(context.Context, continuity.AttentionItem) (continuity.AttentionItem, error)
}
func registerCreatorAttentionRoutes(mux *http.ServeMux, identityClient creatorIdentityClient, repository attentionRepository) {
@@ -61,6 +62,53 @@ func registerCreatorAttentionRoutes(mux *http.ServeMux, identityClient creatorId
w.Header().Set("Cache-Control", "no-store")
_ = json.NewEncoder(w).Encode(result)
})
mux.HandleFunc("POST /v1/creator/attention", func(w http.ResponseWriter, r *http.Request) {
session, ok := creatorSessionForAttention(w, r, identityClient)
if !ok {
return
}
var request continuity.CreateAttentionRequest
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
http.Error(w, "invalid attention request", http.StatusBadRequest)
return
}
if request.OwnerPrincipalID == "" {
request.OwnerPrincipalID = session.PrincipalID
}
if request.OwnerPrincipalID != session.PrincipalID || request.ScopeID == "" || request.SourceKind == "" || request.Level == "" || request.Summary == "" || request.CreatedAtUnixMillis < 1 || request.DueAtUnixMillis < 1 {
http.Error(w, "invalid attention request", http.StatusBadRequest)
return
}
if request.SourceKind == continuity.AttentionSourceAgentDelegation && request.SourceExternalRef == "" {
http.Error(w, "delegation attention requires source external ref", http.StatusBadRequest)
return
}
item := continuity.AttentionItem{
ID: continuity.AttentionItemID(request.SourceExternalRef),
OwnerPrincipalID: request.OwnerPrincipalID,
ScopeID: request.ScopeID,
SourceKind: request.SourceKind,
SourceProspectiveID: request.SourceProspectiveID,
SourceExternalRef: request.SourceExternalRef,
Level: request.Level,
State: continuity.AttentionPending,
Summary: request.Summary,
DueAtUnixMillis: request.DueAtUnixMillis,
CreatedAtUnixMillis: request.CreatedAtUnixMillis,
Revision: 1,
}
saved, err := repository.CreateAttentionItem(r.Context(), item)
if err != nil {
http.Error(w, "attention unavailable", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(saved)
})
}
func creatorSessionForAttention(w http.ResponseWriter, r *http.Request, identityClient creatorIdentityClient) (identity.CreatorSession, bool) {
@@ -98,6 +146,15 @@ func humanAttentionState(state continuity.AttentionState) string {
}
func humanResponseHint(item continuity.AttentionItem) string {
if item.SourceKind == continuity.AttentionSourceAgentDelegation {
if item.State == continuity.AttentionDelivered || item.State == continuity.AttentionPending || item.State == continuity.AttentionFailed {
return "Можно ответить: «Продолжай» или «Нет, нужен другой вариант»."
}
if item.ResponseOutcome != "" {
return "Ответ записан: " + string(item.ResponseOutcome)
}
return ""
}
if item.State == continuity.AttentionDelivered || item.State == continuity.AttentionPending || item.State == continuity.AttentionFailed {
return "Можно ответить: «Готово», «Отложи на 10 минут» или «Отмени»."
}
@@ -121,5 +178,8 @@ func humanAttentionSource(kind continuity.AttentionSourceKind) string {
if kind == continuity.AttentionSourceProspectiveReminder {
return "напоминание"
}
if kind == continuity.AttentionSourceAgentDelegation {
return "задача coding-agent"
}
return "событие Домового"
}
+126
View File
@@ -19,7 +19,9 @@ import (
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
"git.cin.su/m/Assistant/contracts/work"
capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain"
workdomain "git.cin.su/m/Assistant/modules/work/domain"
"git.cin.su/m/Assistant/platform/ids"
)
@@ -33,6 +35,11 @@ type continuityResponseClient struct {
client *http.Client
}
type continuityAttentionClient struct {
baseURL *url.URL
client *http.Client
}
func newContinuityReminderClient(rawURL string, client *http.Client) (continuityReminderClient, error) {
base, err := url.Parse(rawURL)
if err != nil || base.Scheme == "" || base.Host == "" {
@@ -55,6 +62,17 @@ func newContinuityResponseClient(rawURL string, client *http.Client) (continuity
return continuityResponseClient{baseURL: base, client: client}, nil
}
func newContinuityAttentionClient(rawURL string, client *http.Client) (continuityAttentionClient, error) {
base, err := url.Parse(rawURL)
if err != nil || base.Scheme == "" || base.Host == "" {
return continuityAttentionClient{}, fmt.Errorf("invalid Continuity Runtime URL")
}
if client == nil {
client = http.DefaultClient
}
return continuityAttentionClient{baseURL: base, client: client}, nil
}
func (c continuityReminderClient) Schedule(ctx context.Context, authorization string, request continuity.ScheduleReminderRequest) (continuity.ScheduleReminderResult, error) {
target := *c.baseURL
target.Path = "/v1/creator/reminders"
@@ -114,6 +132,37 @@ func (c continuityResponseClient) Respond(ctx context.Context, authorization str
return result, nil
}
func (c continuityAttentionClient) Create(ctx context.Context, authorization string, request continuity.CreateAttentionRequest) (continuity.AttentionItem, error) {
target := *c.baseURL
target.Path = "/v1/creator/attention"
payload, err := json.Marshal(request)
if err != nil {
return continuity.AttentionItem{}, err
}
upstream, err := http.NewRequestWithContext(ctx, http.MethodPost, target.String(), bytes.NewReader(payload))
if err != nil {
return continuity.AttentionItem{}, err
}
upstream.Header.Set("Content-Type", "application/json")
upstream.Header.Set("Authorization", authorization)
response, err := c.client.Do(upstream)
if err != nil {
return continuity.AttentionItem{}, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusCreated {
return continuity.AttentionItem{}, fmt.Errorf("continuity attention status %d", response.StatusCode)
}
var result continuity.AttentionItem
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
return continuity.AttentionItem{}, err
}
if result.ID == "" || result.SourceKind == "" {
return continuity.AttentionItem{}, fmt.Errorf("invalid attention result")
}
return result, nil
}
func resolveChatCapability(ctx context.Context, repository *postgres.CapabilityRepository, kind capability.CapabilityKind, session identity.CreatorSession, intent interaction.OriginalHumanIntent, utterance interaction.HumanUtterance, now time.Time) (capability.CapabilityResolution, error) {
requirementID, err := ids.New()
if err != nil {
@@ -127,6 +176,7 @@ func resolveChatCapability(ctx context.Context, repository *postgres.CapabilityR
OriginalIntentID: capability.OriginalIntentRef(intent.ID),
SourceUtteranceID: capability.SourceUtteranceRef(utterance.ID),
RequiredConstraints: []capability.DataConstraint{capability.DataConstraintLocalOnly},
AllowCanary: allowCanaryForChatCapability(kind),
AuthorityContextRef: capability.AuthorityPolicyCreatorDirectiveV1,
ObservedAtUnixMillis: now.UnixMilli(),
}
@@ -144,6 +194,82 @@ func resolveChatCapability(ctx context.Context, repository *postgres.CapabilityR
return resolution, err
}
func allowCanaryForChatCapability(kind capability.CapabilityKind) bool {
switch kind {
case capability.CapabilityKindModifyCode, capability.CapabilityKindRunTests, capability.CapabilityKindInspectProject:
return true
default:
return false
}
}
type createdDelegation struct {
Delegation work.Delegation
Run work.AgentRun
}
func maybeCreateAgentDelegation(ctx context.Context, workRepository *postgres.WorkRepository, capabilityRepository *postgres.CapabilityRepository, session identity.CreatorSession, conversation interaction.Conversation, intent interaction.OriginalHumanIntent, utterance interaction.HumanUtterance, planning work.PlanningResult, now time.Time) (*createdDelegation, capability.CapabilityResolution, error) {
requirement, ok := primarySoftwareRequirement(planning.Candidate.Requirements)
if !ok {
return nil, capability.CapabilityResolution{}, nil
}
resolution, err := resolveChatCapability(ctx, capabilityRepository, requirement.CapabilityKind, session, intent, utterance, now)
if err != nil {
return nil, resolution, err
}
if len(resolution.Eligible) == 0 || resolution.Eligible[0].Implementation.Kind != capability.ImplementationAgent {
return nil, resolution, capabilitydomain.ErrNoCapabilityImplementation
}
started, err := workdomain.StartDelegation(workdomain.DelegationRequest{
Goal: planning.Goal,
Plan: planning.Plan,
Candidate: planning.Candidate,
Requirement: requirement,
Implementation: resolution.Eligible[0].Implementation,
ConversationID: work.ConversationRef(conversation.ID),
OriginalIntentText: intent.Text,
ContextSummary: planning.Candidate.Summary,
ContextRefs: []string{"interaction:intent:" + string(intent.ID), "interaction:utterance:" + string(utterance.ID), "work:plan:" + string(planning.Plan.ID)},
VerificationSummary: "verify through changed files, relevant tests, and explicit run evidence before work completion",
DerivedInstruction: buildSoftwareAgentInstruction(planning, intent, utterance),
StartedAt: now,
})
if err != nil {
return nil, capability.CapabilityResolution{}, err
}
if err := workRepository.SaveDelegationBundle(ctx, started.Delegation, started.Run); err != nil {
return nil, capability.CapabilityResolution{}, err
}
return &createdDelegation{Delegation: started.Delegation, Run: started.Run}, resolution, nil
}
func primarySoftwareRequirement(values []work.CapabilityRequirement) (work.CapabilityRequirement, bool) {
for _, requirement := range values {
switch requirement.CapabilityKind {
case capability.CapabilityKindModifyCode, capability.CapabilityKindRunTests, capability.CapabilityKindInspectProject:
return requirement, true
}
}
return work.CapabilityRequirement{}, false
}
func buildSoftwareAgentInstruction(planning work.PlanningResult, intent interaction.OriginalHumanIntent, utterance interaction.HumanUtterance) string {
steps := make([]string, 0, len(planning.Candidate.Steps))
for _, step := range planning.Candidate.Steps {
if step.Description != "" {
steps = append(steps, step.Description)
}
}
return strings.TrimSpace(strings.Join([]string{
"Human asked: " + strings.TrimSpace(intent.Text),
"Original utterance: " + strings.TrimSpace(utterance.Content),
"Desired outcome: " + planning.Goal.DesiredOutcome,
"Plan summary: " + planning.Candidate.Summary,
"Verification: run relevant tests and keep explicit evidence before claiming completion.",
"Steps: " + strings.Join(steps, " | "),
}, "\n"))
}
func observeCapabilityUsage(ctx context.Context, repository *postgres.CapabilityRepository, selected capability.ResolvedCapabilityImplementation, requirement capability.CapabilityResolutionRequirement, outcome capability.UsageOutcomeClass, outcomeRef string, started time.Time, now time.Time) (capability.CapabilityUsageObservationID, error) {
id, err := ids.New()
if err != nil {
+28
View File
@@ -9,9 +9,11 @@ import (
"testing"
"time"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/continuity"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
"git.cin.su/m/Assistant/contracts/work"
)
func TestBuildReminderRequestParsesTomorrowAtTen(t *testing.T) {
@@ -72,3 +74,29 @@ func TestReminderSummaryRemovesSchedulingWords(t *testing.T) {
t.Fatalf("summary = %q", summary)
}
}
func TestBuildSoftwareAgentInstructionKeepsHumanIntentSeparateFromDerivedContext(t *testing.T) {
instruction := buildSoftwareAgentInstruction(
work.PlanningResult{
Goal: work.Goal{DesiredOutcome: "fix chat wording"},
Candidate: work.PlanCandidate{Summary: "accepted deterministic software change plan", Steps: []work.PlanStep{{Description: "Inspect chat surface"}, {Description: "Change copy"}, {Description: "Run tests"}}},
},
interaction.OriginalHumanIntent{ID: "intent-1", Text: "Исправь текст в чате Assistant."},
interaction.HumanUtterance{ID: "utterance-1", Content: "Исправь текст в чате Assistant."},
)
if !strings.Contains(instruction, "Human asked: Исправь текст в чате Assistant.") {
t.Fatalf("instruction must preserve human intent: %q", instruction)
}
if !strings.Contains(instruction, "Verification: run relevant tests") || !strings.Contains(instruction, "Plan summary: accepted deterministic software change plan") {
t.Fatalf("instruction must include derived execution context: %q", instruction)
}
}
func TestAllowCanaryForChatCapabilityIsLimitedToSoftwareEngineeringPath(t *testing.T) {
if !allowCanaryForChatCapability(capability.CapabilityKindModifyCode) || !allowCanaryForChatCapability(capability.CapabilityKindRunTests) || !allowCanaryForChatCapability(capability.CapabilityKindInspectProject) {
t.Fatal("software-engineering capabilities must allow canary implementations")
}
if allowCanaryForChatCapability(capability.CapabilityKindScheduleReminder) {
t.Fatal("non software-engineering capability must not implicitly allow canary")
}
}
+51
View File
@@ -3,6 +3,7 @@ package main
import (
"context"
"log"
"strconv"
"strings"
"time"
@@ -68,6 +69,20 @@ type capabilityLearningInput struct {
At time.Time
}
type delegationLearningInput struct {
Intent interaction.OriginalHumanIntent
Utterance interaction.HumanUtterance
DelegationID string
RunID string
ImplementationID capability.CapabilityImplementationID
OutcomeRef string
VerificationRefs []epistemics.EvidenceRef
Verified bool
ExecutionWindows int
HumanBlocked bool
At time.Time
}
func (r learningRecorder) recordReminderResponse(ctx context.Context, input reminderResponseLearningInput) {
if r.epistemics == nil || r.capability == nil || input.Intent.ID == "" || input.Utterance.ID == "" || input.Result.Response.ID == "" {
return
@@ -200,6 +215,42 @@ func (r learningRecorder) recordCapabilityUse(ctx context.Context, input capabil
r.saveExperienceAndReflection(ctx, experience)
}
func (r learningRecorder) recordDelegationOutcome(ctx context.Context, input delegationLearningInput) {
if r.epistemics == nil || input.Intent.ID == "" || input.Utterance.ID == "" || input.ImplementationID == "" {
return
}
actual := epistemics.OutcomeSuccess
confidence := int64(950)
if !input.Verified {
actual = epistemics.OutcomeFailed
confidence = 850
}
detailParts := []string{"delegation outcome recorded"}
if input.ExecutionWindows > 1 {
detailParts = append(detailParts, "execution-window-continuations="+strconv.Itoa(input.ExecutionWindows-1))
}
if input.HumanBlocked {
detailParts = append(detailParts, "human-blocker-occurred")
}
experience := epistemics.ExperienceRecord{
SourceKind: epistemics.ExperienceSourceCapability,
OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID),
SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID),
ContextRefs: contextRefs("work:delegation:"+input.DelegationID, "work:run:"+input.RunID),
HelpMethod: epistemics.HelpMethodCapabilityExecution,
HelpMethodRef: "work:agent-delegation-v1",
CapabilityImplementationRef: epistemics.CapabilityImplementationRef(input.ImplementationID),
ExpectedOutcome: epistemics.OutcomeSuccess,
ActualOutcome: actual,
OutcomeRef: input.OutcomeRef,
OutcomeDetail: strings.Join(detailParts, "; "),
VerificationEvidence: input.VerificationRefs,
ConfidenceMillis: confidence,
RecordedAtUnixMillis: input.At.UnixMilli(),
}
r.saveExperienceAndReflection(ctx, experience)
}
func (r learningRecorder) saveExperienceAndReflection(ctx context.Context, value epistemics.ExperienceRecord) (epistemics.ExperienceRecord, bool) {
value, err := epistemicsdomain.NewExperienceRecord(value)
if err != nil {
+278
View File
@@ -107,6 +107,10 @@ func main() {
if err != nil {
log.Fatal(err)
}
attentionClient, err := newContinuityAttentionClient(envOr("CONTINUITY_RUNTIME_URL", "http://continuity-runtime:8081"), &http.Client{Timeout: 5 * 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) {
@@ -327,6 +331,9 @@ func main() {
planningReply = responseResult.Reply
needsCognition = false
learning.recordReminderResponse(r.Context(), reminderResponseLearningInput{Utterance: utterance, Intent: intent, Result: responseResult, At: now})
if resumed, err := continueDelegationFromHumanResponse(r.Context(), workRepository, responseResult.Response, now); err == nil && resumed {
planningReply = "Принял. Продолжаю ту же задачу coding-agent без потери контекста."
}
} else if responseErr == nil && looksLikeUnsupportedContinuation(utterance.Content, responseResult) {
learning.recordFastPathDecline(r.Context(), fastPathDeclineLearningInput{Utterance: utterance, Intent: intent, Result: responseResult, Reason: declineReasonForResponse(responseResult), At: now})
}
@@ -365,6 +372,8 @@ func main() {
var capabilityStatus capability.CapabilityResolutionStatus
var capabilityKind capability.CapabilityKind
var capabilityGapID capability.CapabilityGapID
var delegationID work.DelegationID
var delegationState work.DelegationState
var reminder *continuity.ProspectiveItem
if helpPath.Kind == work.HelpPathDirectCapability && len(helpPath.SuggestedCapabilityKinds) > 0 {
capabilityKind = helpPath.SuggestedCapabilityKinds[0]
@@ -423,6 +432,18 @@ func main() {
planningReply = planning.Reply
workPlanned = true
needsCognition = false
if created, resolution, createErr := maybeCreateAgentDelegation(r.Context(), workRepository, capabilityRepository, session, conversation, intent, utterance, planning, now); createErr == nil && created != nil {
delegationID = created.Delegation.ID
delegationState = created.Delegation.State
capabilityKind = created.Delegation.CapabilityKind
capabilityStatus = resolution.Status
planningReply = "Понял задачу. Я собрал work item, выбрал coding-agent в CANARY-режиме и создал delegation. Буду вести её как отдельный run с checkpoint/resume, без потери исходного human intent."
} else if resolution.Gap != nil {
capabilityKind = resolution.Requirement.CapabilityKind
capabilityStatus = resolution.Status
capabilityGapID = resolution.Gap.ID
planningReply = planningReply + " Для coding delegation подходящий agent implementation сейчас недоступен, поэтому я сохранил план без ложного запуска."
}
}
response := interaction.ChatMessageResponse{
ConversationID: conversation.ID,
@@ -440,6 +461,8 @@ func main() {
CapabilityStatus: capabilityStatus,
CapabilityKind: capabilityKind,
CapabilityGapID: capabilityGapID,
DelegationID: string(delegationID),
DelegationState: string(delegationState),
Reminder: reminder,
}
w.Header().Set("Content-Type", "application/json")
@@ -447,6 +470,210 @@ func main() {
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(response)
})
mux.HandleFunc("GET /v1/creator/delegations/{id}", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
value, err := workRepository.LoadDelegation(r.Context(), work.DelegationID(r.PathValue("id")))
if err != nil {
http.Error(w, "delegation not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(value)
})
mux.HandleFunc("GET /v1/creator/delegations/{id}/latest-checkpoint", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
value, err := workRepository.LatestAgentCheckpoint(r.Context(), work.DelegationID(r.PathValue("id")))
if err != nil {
http.Error(w, "checkpoint not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(value)
})
mux.HandleFunc("POST /v1/creator/delegations/{id}/runs/{run}/checkpoints", func(w http.ResponseWriter, r *http.Request) {
session, ok := authenticateCreator(w, r)
if !ok {
return
}
var request struct {
BlockerClass work.AgentBlockerClass
CompletedSummary string
RemainingSummary string
TestSummary string
BranchName string
CommitHash string
NextStep string
ArtifactRefs []work.EvidenceRef
RecordedAtUnixMillis int64
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
http.Error(w, "invalid checkpoint", http.StatusBadRequest)
return
}
run, err := workRepository.LoadAgentRun(r.Context(), work.AgentRunID(r.PathValue("run")))
if err != nil || run.DelegationID != work.DelegationID(r.PathValue("id")) {
http.Error(w, "run not found", http.StatusNotFound)
return
}
updatedRun, checkpoint, err := workdomain.CheckpointRun(run, request.BlockerClass, request.CompletedSummary, request.RemainingSummary, request.TestSummary, request.BranchName, request.CommitHash, request.NextStep, request.ArtifactRefs, time.UnixMilli(request.RecordedAtUnixMillis))
if err != nil {
http.Error(w, "invalid checkpoint", http.StatusBadRequest)
return
}
if err := workRepository.SaveAgentCheckpoint(r.Context(), updatedRun, checkpoint); err != nil {
http.Error(w, "save checkpoint", http.StatusInternalServerError)
return
}
if request.BlockerClass == work.AgentBlockerHumanDecisionRequired {
delegation, err := workRepository.LoadDelegation(r.Context(), run.DelegationID)
if err == nil {
_, _ = attentionClient.Create(r.Context(), r.Header.Get("Authorization"), continuity.CreateAttentionRequest{
OwnerPrincipalID: session.PrincipalID,
ScopeID: authority.ScopeID("domovoy-global-scope"),
SourceKind: continuity.AttentionSourceAgentDelegation,
SourceExternalRef: delegationAttentionRef(delegation.ID),
Level: continuity.AttentionActionRequired,
Summary: "Нужен твой выбор по задаче coding-agent: " + firstNonEmpty(request.RemainingSummary, delegation.DesiredOutcome),
DueAtUnixMillis: checkpoint.RecordedAtUnixMillis,
CreatedAtUnixMillis: checkpoint.RecordedAtUnixMillis,
})
}
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(checkpoint)
})
mux.HandleFunc("POST /v1/creator/delegations/{id}/runs/{run}/results", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
var request struct {
Summary string
OutcomeRef string
VerificationSummary string
ArtifactRefs []work.EvidenceRef
RecordedAtUnixMillis int64
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
http.Error(w, "invalid result", http.StatusBadRequest)
return
}
run, err := workRepository.LoadAgentRun(r.Context(), work.AgentRunID(r.PathValue("run")))
if err != nil || run.DelegationID != work.DelegationID(r.PathValue("id")) {
http.Error(w, "run not found", http.StatusNotFound)
return
}
updatedRun, result, err := workdomain.CompleteRun(run, request.Summary, request.OutcomeRef, request.VerificationSummary, request.ArtifactRefs, time.UnixMilli(request.RecordedAtUnixMillis))
if err != nil {
http.Error(w, "invalid result", http.StatusBadRequest)
return
}
if err := workRepository.SaveAgentResult(r.Context(), updatedRun, result); err != nil {
http.Error(w, "save result", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(result)
})
mux.HandleFunc("POST /v1/creator/delegations/{id}/resume", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
delegation, err := workRepository.LoadDelegation(r.Context(), work.DelegationID(r.PathValue("id")))
if err != nil {
http.Error(w, "delegation not found", http.StatusNotFound)
return
}
run, err := workRepository.LoadAgentRun(r.Context(), delegation.LatestRunID)
if err != nil {
http.Error(w, "run not found", http.StatusNotFound)
return
}
checkpoint, err := workRepository.LatestAgentCheckpoint(r.Context(), delegation.ID)
if err != nil {
http.Error(w, "checkpoint not found", http.StatusNotFound)
return
}
updatedDelegation, resumedRun, err := workdomain.ResumeDelegationRun(delegation, run, checkpoint, time.Now())
if err != nil {
http.Error(w, "resume not allowed", http.StatusConflict)
return
}
if err := workRepository.SaveResumedRun(r.Context(), updatedDelegation, resumedRun); err != nil {
http.Error(w, "save resumed run", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(resumedRun)
})
mux.HandleFunc("POST /v1/creator/delegations/{id}/verify", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
var request struct {
Verified bool
Summary string
EvidenceRefs []work.EvidenceRef
RecordedAtUnixMillis int64
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil || request.RecordedAtUnixMillis < 1 {
http.Error(w, "invalid verification", http.StatusBadRequest)
return
}
delegation, err := workRepository.LoadDelegation(r.Context(), work.DelegationID(r.PathValue("id")))
if err != nil {
http.Error(w, "delegation not found", http.StatusNotFound)
return
}
run, err := workRepository.LoadAgentRun(r.Context(), delegation.LatestRunID)
if err != nil {
http.Error(w, "run not found", http.StatusNotFound)
return
}
result, err := workRepository.LatestAgentResult(r.Context(), delegation.ID)
if err != nil {
http.Error(w, "result not found", http.StatusNotFound)
return
}
updatedDelegation, verification, err := workdomain.VerifyDelegationResult(delegation, run, result, request.Verified, request.Summary, request.EvidenceRefs, time.UnixMilli(request.RecordedAtUnixMillis))
if err != nil {
http.Error(w, "invalid verification", http.StatusBadRequest)
return
}
if err := workRepository.SaveDelegationVerification(r.Context(), updatedDelegation, verification); err != nil {
http.Error(w, "save verification", http.StatusInternalServerError)
return
}
runCount, _ := workRepository.CountAgentRuns(r.Context(), delegation.ID)
learning.recordDelegationOutcome(r.Context(), delegationLearningInput{
Intent: interaction.OriginalHumanIntent{ID: interaction.OriginalHumanIntentID(delegation.SourceIntentID), Text: delegation.DesiredOutcome},
Utterance: interaction.HumanUtterance{ID: interaction.HumanUtteranceID(delegation.SourceUtteranceID), Content: delegation.DesiredOutcome},
DelegationID: string(delegation.ID),
RunID: string(run.ID),
ImplementationID: run.ImplementationID,
OutcomeRef: result.OutcomeRef,
VerificationRefs: evidenceRefsToEpistemics(request.EvidenceRefs),
Verified: request.Verified,
ExecutionWindows: runCount,
HumanBlocked: false,
At: time.UnixMilli(request.RecordedAtUnixMillis),
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(verification)
})
mux.HandleFunc("GET /v1/principal-bindings/{actor}/{principal}", func(w http.ResponseWriter, r *http.Request) {
binding, err := identityRepository.LoadBinding(r.Context(), identity.ActorID(r.PathValue("actor")), identity.PrincipalID(r.PathValue("principal")))
if err != nil {
@@ -859,3 +1086,54 @@ func humanCapabilityList(kinds []capability.CapabilityKind) string {
}
return strings.Join(labels, ", ")
}
func delegationAttentionRef(id work.DelegationID) string {
return "work:delegation:" + string(id)
}
func continueDelegationFromHumanResponse(ctx context.Context, repository *postgres.WorkRepository, response continuity.HumanResponse, now time.Time) (bool, error) {
if response.Operation != continuity.ResponseContinue || !strings.HasPrefix(response.TargetExternalRef, "work:delegation:") {
return false, nil
}
delegationID := work.DelegationID(strings.TrimPrefix(response.TargetExternalRef, "work:delegation:"))
delegation, err := repository.LoadDelegation(ctx, delegationID)
if err != nil {
return false, err
}
run, err := repository.LoadAgentRun(ctx, delegation.LatestRunID)
if err != nil {
return false, err
}
checkpoint, err := repository.LatestAgentCheckpoint(ctx, delegationID)
if err != nil {
return false, err
}
updatedDelegation, resumedRun, err := workdomain.ResumeDelegationRun(delegation, run, checkpoint, now)
if err != nil {
return false, err
}
if err := repository.SaveResumedRun(ctx, updatedDelegation, resumedRun); err != nil {
return false, err
}
return true, nil
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return strings.TrimSpace(value)
}
}
return ""
}
func evidenceRefsToEpistemics(values []work.EvidenceRef) []epistemics.EvidenceRef {
result := make([]epistemics.EvidenceRef, 0, len(values))
for _, value := range values {
if strings.TrimSpace(string(value)) == "" {
continue
}
result = append(result, epistemics.EvidenceRef(value))
}
return result
}
+18 -1
View File
@@ -154,6 +154,8 @@ type browserChatResponse struct {
WorkPlanned bool `json:"work_planned"`
CapabilityStatus string `json:"capability_status"`
CapabilityKind string `json:"capability_kind"`
DelegationID string `json:"delegation_id"`
DelegationState string `json:"delegation_state"`
ReminderScheduled bool `json:"reminder_scheduled"`
}
@@ -353,7 +355,22 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
_ = json.NewEncoder(w).Encode(browserChatResponse{ConversationID: string(saved.ConversationID), UtteranceID: string(saved.UtteranceID), SpeechAct: string(saved.SpeechAct), ReplyText: reply, Cognition: cognition, ActiveFocus: saved.ActiveProjectName, ProjectProvenance: string(saved.ProjectProvenance), Clarification: string(saved.Clarification), WorkPlanned: saved.WorkPlanned, CapabilityStatus: string(saved.CapabilityStatus), CapabilityKind: string(saved.CapabilityKind), ReminderScheduled: saved.Reminder != nil})
_ = json.NewEncoder(w).Encode(browserChatResponse{
ConversationID: string(saved.ConversationID),
UtteranceID: string(saved.UtteranceID),
SpeechAct: string(saved.SpeechAct),
ReplyText: reply,
Cognition: cognition,
ActiveFocus: saved.ActiveProjectName,
ProjectProvenance: string(saved.ProjectProvenance),
Clarification: string(saved.Clarification),
WorkPlanned: saved.WorkPlanned,
CapabilityStatus: string(saved.CapabilityStatus),
CapabilityKind: string(saved.CapabilityKind),
DelegationID: saved.DelegationID,
DelegationState: saved.DelegationState,
ReminderScheduled: saved.Reminder != nil,
})
}
func asksAttention(message string) bool {
+20
View File
@@ -62,11 +62,31 @@ async function api(path, options = {}) {
function humanMeta(result) {
if (result.reminder_scheduled) return "Напоминание сохранено. Я вернусь к нему сам.";
if (result.delegation_id && result.delegation_state) return `Передал в coding-agent: ${humanDelegationState(result.delegation_state)}.`;
if (result.capability_status && result.capability_status !== "AVAILABLE") return "Возможность пока не подключена.";
if (result.work_planned) return "Я разложил это как работу.";
return "";
}
function humanDelegationState(state) {
switch (state) {
case "RUNNING":
return "работа запущена";
case "WAITING":
return "ожидаю следующий шаг";
case "BLOCKED":
return "нужно решение человека";
case "VERIFYING":
return "результат проверяется";
case "COMPLETED":
return "работа завершена";
case "FAILED":
return "run завершился ошибкой";
default:
return state.toLowerCase();
}
}
function renderHistory() {
if (!historyList) return;
historyList.replaceChildren();
+16
View File
@@ -58,6 +58,7 @@ type AttentionSourceKind string
const (
AttentionSourceProspectiveReminder AttentionSourceKind = "PROSPECTIVE_REMINDER"
AttentionSourceAgentDelegation AttentionSourceKind = "AGENT_DELEGATION"
)
// ProspectiveItem is canonical future relevance. TriggerAtUnixMillis is an
@@ -99,6 +100,7 @@ type AttentionItem struct {
ScopeID authority.ScopeID
SourceKind AttentionSourceKind
SourceProspectiveID ProspectiveItemID
SourceExternalRef string
Level AttentionLevel
State AttentionState
Summary string
@@ -177,6 +179,8 @@ const (
ResponseOutcomeCancelled ResponseOutcome = "CANCELLED"
ResponseOutcomeDismissed ResponseOutcome = "DISMISSED"
ResponseOutcomeClarificationNeeded ResponseOutcome = "CLARIFICATION_NEEDED"
ResponseOutcomeContinued ResponseOutcome = "CONTINUED"
ResponseOutcomeRejected ResponseOutcome = "REJECTED"
ResponseOutcomeNoop ResponseOutcome = "NOOP"
)
@@ -213,6 +217,18 @@ type HumanResponseResult struct {
Reply string
}
type CreateAttentionRequest struct {
OwnerPrincipalID identity.PrincipalID
ScopeID authority.ScopeID
SourceKind AttentionSourceKind
SourceProspectiveID ProspectiveItemID
SourceExternalRef string
Level AttentionLevel
Summary string
DueAtUnixMillis int64
CreatedAtUnixMillis int64
}
// SatisfyDeliveredSubject is the complete serializable operation subject. It
// is distinct from a Work TaskTarget, which remains the live justification.
type SatisfyDeliveredSubject struct {
+2
View File
@@ -136,5 +136,7 @@ type ChatMessageResponse struct {
CapabilityStatus capability.CapabilityResolutionStatus
CapabilityKind capability.CapabilityKind
CapabilityGapID capability.CapabilityGapID
DelegationID string
DelegationState string
Reminder *continuity.ProspectiveItem
}
+120
View File
@@ -16,6 +16,11 @@ type WorkPlanID ids.ID
type PlanStepID ids.ID
type CapabilityRequirementID ids.ID
type DelegationCandidateID ids.ID
type DelegationID ids.ID
type AgentRunID ids.ID
type AgentCheckpointID ids.ID
type AgentResultID ids.ID
type DelegationVerificationID ids.ID
type TaskRevision uint64
type GoalRevision uint64
type PlanRevision uint64
@@ -25,6 +30,11 @@ type PlanStepKind string
type CapabilityRequirementState string
type DelegationCandidateState string
type DelegationKind string
type DelegationState string
type AgentRunState string
type AgentBlockerClass string
type AgentExecutionProtocol string
type EvidenceRef string
type SourceIntentRef string
type SourceUtteranceRef string
type ConversationRef string
@@ -78,6 +88,35 @@ const (
DelegationKindHuman DelegationKind = "HUMAN"
DelegationKindUnavailable DelegationKind = "UNAVAILABLE"
DelegationPending DelegationState = "PENDING"
DelegationRunning DelegationState = "RUNNING"
DelegationWaiting DelegationState = "WAITING"
DelegationBlocked DelegationState = "BLOCKED"
DelegationVerifying DelegationState = "VERIFYING"
DelegationCompleted DelegationState = "COMPLETED"
DelegationFailed DelegationState = "FAILED"
DelegationCancelled DelegationState = "CANCELLED"
AgentRunStarted AgentRunState = "STARTED"
AgentRunRunning AgentRunState = "RUNNING"
AgentRunWaiting AgentRunState = "WAITING"
AgentRunCheckpointed AgentRunState = "CHECKPOINTED"
AgentRunExecutionWindowExhausted AgentRunState = "EXECUTION_WINDOW_EXHAUSTED"
AgentRunBlocked AgentRunState = "BLOCKED"
AgentRunFailed AgentRunState = "FAILED"
AgentRunCompleted AgentRunState = "COMPLETED"
AgentRunCancelled AgentRunState = "CANCELLED"
AgentBlockerNone AgentBlockerClass = "NONE"
AgentBlockerHumanDecisionRequired AgentBlockerClass = "HUMAN_DECISION_REQUIRED"
AgentBlockerMissingExternalCredential AgentBlockerClass = "MISSING_EXTERNAL_CREDENTIAL"
AgentBlockerArchitectureDecision AgentBlockerClass = "ARCHITECTURE_DECISION_REQUIRED"
AgentBlockerSecurityConflict AgentBlockerClass = "SECURITY_CONFLICT"
AgentBlockerTechnicalFailure AgentBlockerClass = "TECHNICAL_FAILURE"
AgentBlockerExecutionWindow AgentBlockerClass = "EXECUTION_WINDOW_EXHAUSTED"
AgentProtocolCodexBridgeV1 AgentExecutionProtocol = "CODEX_BRIDGE_V1"
HelpPathAnswer HelpPathKind = "ANSWER"
HelpPathCognition HelpPathKind = "COGNITION"
HelpPathDirectCapability HelpPathKind = "DIRECT_CAPABILITY"
@@ -168,6 +207,87 @@ type WorkPlan struct {
RecordedAtUnixMillis int64
}
type Delegation struct {
ID DelegationID
GoalID GoalID
PlanID WorkPlanID
PlanCandidateID PlanCandidateID
CapabilityRequirementID CapabilityRequirementID
CapabilityKind capability.CapabilityKind
SelectedImplementationID capability.CapabilityImplementationID
SelectedImplementationRev int64
SelectedImplementationKind capability.ImplementationKind
Protocol AgentExecutionProtocol
SourceIntentID SourceIntentRef
SourceUtteranceID SourceUtteranceRef
ConversationID ConversationRef
ProjectID ProjectRef
ProjectDisplayName string
DesiredOutcome string
ConstraintSummary string
VerificationSummary string
ContextSummary string
ContextRefs []string
State DelegationState
LatestRunID AgentRunID
LatestResultID AgentResultID
CreatedAtUnixMillis int64
UpdatedAtUnixMillis int64
}
type AgentRun struct {
ID AgentRunID
DelegationID DelegationID
ImplementationID capability.CapabilityImplementationID
ImplementationRevision int64
Protocol AgentExecutionProtocol
State AgentRunState
DerivedInstructionSummary string
StartedAtUnixMillis int64
UpdatedAtUnixMillis int64
CompletedAtUnixMillis int64
}
type AgentCheckpoint struct {
ID AgentCheckpointID
DelegationID DelegationID
RunID AgentRunID
State AgentRunState
BlockerClass AgentBlockerClass
CompletedSummary string
RemainingSummary string
TestSummary string
BranchName string
CommitHash string
NextStep string
ArtifactRefs []EvidenceRef
RecordedAtUnixMillis int64
}
type AgentResult struct {
ID AgentResultID
DelegationID DelegationID
RunID AgentRunID
State AgentRunState
Summary string
OutcomeRef string
VerificationRequired bool
VerificationSummary string
ArtifactRefs []EvidenceRef
RecordedAtUnixMillis int64
}
type DelegationVerification struct {
ID DelegationVerificationID
DelegationID DelegationID
RunID AgentRunID
ResultID AgentResultID
Verified bool
Summary string
EvidenceRefs []EvidenceRef
RecordedAtUnixMillis int64
}
type PlanningInput struct {
ConversationID ConversationRef
UtteranceID SourceUtteranceRef
@@ -0,0 +1,145 @@
BEGIN;
SET ROLE work_role;
CREATE TABLE IF NOT EXISTS work.delegations (
delegation_id TEXT PRIMARY KEY,
goal_id TEXT NOT NULL,
work_plan_id TEXT NOT NULL,
plan_candidate_id TEXT NOT NULL,
capability_requirement_id TEXT NOT NULL,
capability_kind TEXT NOT NULL,
selected_implementation_id TEXT NOT NULL,
selected_implementation_revision BIGINT NOT NULL,
selected_implementation_kind TEXT NOT NULL,
protocol TEXT NOT NULL,
source_intent_id TEXT NOT NULL,
source_utterance_id TEXT NOT NULL,
conversation_id TEXT NOT NULL DEFAULT '',
project_id TEXT NOT NULL DEFAULT '',
project_display_name TEXT NOT NULL DEFAULT '',
desired_outcome TEXT NOT NULL,
constraint_summary TEXT NOT NULL DEFAULT '',
verification_summary TEXT NOT NULL DEFAULT '',
context_summary TEXT NOT NULL DEFAULT '',
context_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
state TEXT NOT NULL,
latest_run_id TEXT NOT NULL DEFAULT '',
latest_result_id TEXT NOT NULL DEFAULT '',
created_at_unix_millis BIGINT NOT NULL,
updated_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (delegation_id <> ''),
CHECK (goal_id <> ''),
CHECK (work_plan_id <> ''),
CHECK (plan_candidate_id <> ''),
CHECK (capability_requirement_id <> ''),
CHECK (capability_kind <> ''),
CHECK (selected_implementation_id <> ''),
CHECK (selected_implementation_revision > 0),
CHECK (selected_implementation_kind = 'AGENT'),
CHECK (protocol IN ('CODEX_BRIDGE_V1')),
CHECK (source_intent_id <> ''),
CHECK (source_utterance_id <> ''),
CHECK (desired_outcome <> ''),
CHECK (state IN ('PENDING','RUNNING','WAITING','BLOCKED','VERIFYING','COMPLETED','FAILED','CANCELLED')),
CHECK (created_at_unix_millis > 0),
CHECK (updated_at_unix_millis >= created_at_unix_millis)
);
CREATE TABLE IF NOT EXISTS work.agent_runs (
run_id TEXT PRIMARY KEY,
delegation_id TEXT NOT NULL,
implementation_id TEXT NOT NULL,
implementation_revision BIGINT NOT NULL,
protocol TEXT NOT NULL,
state TEXT NOT NULL,
derived_instruction_summary TEXT NOT NULL DEFAULT '',
started_at_unix_millis BIGINT NOT NULL,
updated_at_unix_millis BIGINT NOT NULL,
completed_at_unix_millis BIGINT NOT NULL DEFAULT 0,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (run_id <> ''),
CHECK (delegation_id <> ''),
CHECK (implementation_id <> ''),
CHECK (implementation_revision > 0),
CHECK (protocol IN ('CODEX_BRIDGE_V1')),
CHECK (state IN ('STARTED','RUNNING','WAITING','CHECKPOINTED','EXECUTION_WINDOW_EXHAUSTED','BLOCKED','FAILED','COMPLETED','CANCELLED')),
CHECK (started_at_unix_millis > 0),
CHECK (updated_at_unix_millis >= started_at_unix_millis),
CHECK (completed_at_unix_millis >= 0)
);
CREATE TABLE IF NOT EXISTS work.agent_checkpoints (
checkpoint_id TEXT PRIMARY KEY,
delegation_id TEXT NOT NULL,
run_id TEXT NOT NULL,
state TEXT NOT NULL,
blocker_class TEXT NOT NULL,
completed_summary TEXT NOT NULL DEFAULT '',
remaining_summary TEXT NOT NULL DEFAULT '',
test_summary TEXT NOT NULL DEFAULT '',
branch_name TEXT NOT NULL DEFAULT '',
commit_hash TEXT NOT NULL DEFAULT '',
next_step TEXT NOT NULL DEFAULT '',
artifact_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
recorded_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (checkpoint_id <> ''),
CHECK (delegation_id <> ''),
CHECK (run_id <> ''),
CHECK (state IN ('STARTED','RUNNING','WAITING','CHECKPOINTED','EXECUTION_WINDOW_EXHAUSTED','BLOCKED','FAILED','COMPLETED','CANCELLED')),
CHECK (blocker_class IN ('NONE','HUMAN_DECISION_REQUIRED','MISSING_EXTERNAL_CREDENTIAL','ARCHITECTURE_DECISION_REQUIRED','SECURITY_CONFLICT','TECHNICAL_FAILURE','EXECUTION_WINDOW_EXHAUSTED')),
CHECK (recorded_at_unix_millis > 0)
);
CREATE TABLE IF NOT EXISTS work.agent_results (
result_id TEXT PRIMARY KEY,
delegation_id TEXT NOT NULL,
run_id TEXT NOT NULL,
state TEXT NOT NULL,
summary TEXT NOT NULL,
outcome_ref TEXT NOT NULL DEFAULT '',
verification_required BOOLEAN NOT NULL,
verification_summary TEXT NOT NULL DEFAULT '',
artifact_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
recorded_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (result_id <> ''),
CHECK (delegation_id <> ''),
CHECK (run_id <> ''),
CHECK (state IN ('STARTED','RUNNING','WAITING','CHECKPOINTED','EXECUTION_WINDOW_EXHAUSTED','BLOCKED','FAILED','COMPLETED','CANCELLED')),
CHECK (summary <> ''),
CHECK (recorded_at_unix_millis > 0)
);
RESET ROLE;
SET ROLE capability_role;
INSERT INTO capability.capability_descriptors(capability_id, capability_kind, display_name, description, revision, recorded_at_unix_millis)
VALUES
('modify-code', 'MODIFY_CODE', 'Code modification', 'Executor-neutral software engineering capability that changes repository code inside an authorized scope.', 1, 1),
('run-tests', 'RUN_TESTS', 'Run tests', 'Executor-neutral software engineering capability that runs repository validation and test commands.', 1, 1),
('inspect-project', 'INSPECT_PROJECT', 'Inspect project', 'Executor-neutral software engineering capability that inspects repository structure and relevant files.', 1, 1)
ON CONFLICT (capability_id) DO NOTHING;
INSERT INTO capability.capability_implementations(
implementation_id, capability_id, capability_kind, owner_principal_id, revision, implementation_kind, target_ref,
credential_ref, qualification, activation, health, authority_policy_ref, data_constraints,
cost_score_millis, latency_score_millis, reliability_millis, observed_at_unix_millis)
VALUES
('codex-agent-modify-code-v1', 'modify-code', 'MODIFY_CODE', 'domovoy-creator-principal', 1, 'AGENT', 'codex://assistant/modify-code', '', 'VALIDATED', 'CANARY', 'AVAILABLE', 'creator-directive-v1', ARRAY['LOCAL_ONLY','NO_CREDENTIAL_FORWARD'], 5, 80, 820, 1),
('codex-agent-run-tests-v1', 'run-tests', 'RUN_TESTS', 'domovoy-creator-principal', 1, 'AGENT', 'codex://assistant/run-tests', '', 'VALIDATED', 'CANARY', 'AVAILABLE', 'creator-directive-v1', ARRAY['LOCAL_ONLY','NO_CREDENTIAL_FORWARD'], 5, 120, 860, 1),
('codex-agent-inspect-project-v1', 'inspect-project', 'INSPECT_PROJECT', 'domovoy-creator-principal', 1, 'AGENT', 'codex://assistant/inspect-project', '', 'VALIDATED', 'CANARY', 'AVAILABLE', 'creator-directive-v1', ARRAY['LOCAL_ONLY','NO_CREDENTIAL_FORWARD'], 5, 40, 900, 1)
ON CONFLICT DO NOTHING;
INSERT INTO capability.capability_implementation_heads(implementation_id, revision, capability_kind, owner_principal_id)
VALUES
('codex-agent-modify-code-v1', 1, 'MODIFY_CODE', 'domovoy-creator-principal'),
('codex-agent-run-tests-v1', 1, 'RUN_TESTS', 'domovoy-creator-principal'),
('codex-agent-inspect-project-v1', 1, 'INSPECT_PROJECT', 'domovoy-creator-principal')
ON CONFLICT (implementation_id) DO NOTHING;
RESET ROLE;
COMMIT;
@@ -0,0 +1,60 @@
BEGIN;
SET ROLE continuity_role;
ALTER TABLE continuity.attention_items
ADD COLUMN IF NOT EXISTS source_external_ref TEXT NOT NULL DEFAULT '';
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'attention_items_source_kind_check_v2'
) THEN
ALTER TABLE continuity.attention_items DROP CONSTRAINT IF EXISTS attention_items_source_kind_check;
ALTER TABLE continuity.attention_items
ADD CONSTRAINT attention_items_source_kind_check_v2
CHECK (source_kind IN ('PROSPECTIVE_REMINDER','AGENT_DELEGATION'));
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'human_responses_outcome_check_v2'
) THEN
ALTER TABLE continuity.human_responses DROP CONSTRAINT IF EXISTS human_responses_outcome_check;
ALTER TABLE continuity.human_responses
ADD CONSTRAINT human_responses_outcome_check_v2
CHECK (outcome IN ('USER_REPORTED_COMPLETE','SNOOZED','CANCELLED','DISMISSED','CLARIFICATION_NEEDED','CONTINUED','REJECTED','NOOP'));
END IF;
END $$;
CREATE UNIQUE INDEX IF NOT EXISTS attention_items_source_external_ref_unique
ON continuity.attention_items(source_kind, source_external_ref)
WHERE source_external_ref <> '';
RESET ROLE;
SET ROLE work_role;
CREATE TABLE IF NOT EXISTS work.delegation_verifications (
verification_id TEXT PRIMARY KEY,
delegation_id TEXT NOT NULL,
run_id TEXT NOT NULL,
result_id TEXT NOT NULL,
verified BOOLEAN NOT NULL,
summary TEXT NOT NULL,
evidence_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
recorded_at_unix_millis BIGINT NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CHECK (delegation_id <> ''),
CHECK (run_id <> ''),
CHECK (result_id <> ''),
CHECK (summary <> ''),
CHECK (recorded_at_unix_millis > 0)
);
RESET ROLE;
COMMIT;
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000068-runtime-experience-reflection-loop.json",
"task_id": "DEV-000068",
"task_file": "dev/tasks/DEV-000069-general-agent-delegation-codex-bridge.json",
"task_id": "DEV-000069",
"task_revision": 1
}
@@ -0,0 +1,163 @@
{
"task_id": "DEV-000069",
"task_revision": 1,
"architecture_revision": "ADR-0017",
"base_repo_revision": "d870e1361d6dcef3866d7339495d83b9756949c4",
"risk_tier": "E",
"intent": "Introduce a provider-neutral external intelligent agent delegation foundation and the first Codex bridge implementation for software-engineering work without making Codex a canonical subsystem.",
"requirement_ids": [
"ADR-0017",
"ADR-0012",
"ADR-0013",
"ADR-0016",
"INV-OWN-001",
"INV-EPI-001",
"INV-TOPO-001",
"General external agent representation",
"Durable delegation and resumable runs",
"Codex bridge as first AGENT implementation",
"Execution-window continuation",
"Delegation experience evidence"
],
"allowed_change_scope": {
"modules": [
"work",
"capability",
"continuity",
"epistemics",
"interaction",
"situation",
"artifact"
],
"paths": [
"contracts/work/**",
"contracts/capability/**",
"contracts/artifact/**",
"contracts/epistemics/**",
"contracts/continuity/**",
"contracts/interaction/**",
"modules/work/**",
"modules/capability/**",
"modules/epistemics/**",
"modules/continuity/**",
"adapters/postgres/**",
"cmd/core-runtime/**",
"cmd/creator-web/**",
"cmd/continuity-runtime/**",
"database/migrations/**",
"deploy/docker-compose.dev.yml",
"deploy/docker-compose.prod.yml",
"deploy/windows/**",
"tests/**",
"docs/PROJECT_STATUS.md",
"architecture/adrs/ADR-0017-general-agent-delegation-codex-bridge.md",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000069-general-agent-delegation-codex-bridge.json"
]
},
"forbidden_change_scope": {
"paths": [
"architecture/ARCHITECTURE_CONSTITUTION.md",
"architecture/architecture-registry.yaml",
"architecture/architecture-registry.json",
"modules/authority/**",
"modules/commit/**",
"cmd/commit-runtime/**",
"security/**",
"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_runtime_secret_file_for_existing_owner": "only if an already-approved bridge/runtime prerequisite requires a least-privilege credential"
},
"contracts": {
"consume": [
"Interaction HumanUtterance / OriginalHumanIntent",
"Situation focus and project resolution",
"Work Goal / PlanCandidate / CapabilityRequirement / DelegationCandidate",
"Capability CapabilityImplementation / CapabilityResolution / CapabilityUsageObservation",
"Continuity Attention / HumanResponse",
"Epistemics ExperienceRecord / ReflectionCandidate",
"Artifact metadata refs"
],
"may_change": [
"contracts/work@v1",
"contracts/capability@v1",
"contracts/artifact@v1",
"contracts/epistemics@v1"
]
},
"semantic_owner": "work",
"canonical_state": [
"CapabilityImplementation(kind=AGENT)",
"Delegation",
"AgentRun",
"AgentCheckpoint",
"AgentResult"
],
"state_transitions": [
"OriginalHumanIntent -> optional Goal/Plan -> CapabilityRequirement -> real Delegation",
"Delegation -> AgentRun STARTED -> RUNNING/WAITING/CHECKPOINTED/EXECUTION_WINDOW_EXHAUSTED/BLOCKED/FAILED/COMPLETED/CANCELLED",
"execution window exhaustion -> durable checkpoint -> resumed run on same higher-level Delegation",
"human-dependent blocker -> AttentionItem -> natural HumanResponse -> continue delegation/work",
"agent result evidence -> verification -> Work completion decision -> ExperienceRecord"
],
"side_effects_and_privileges": [
"No new canonical owner and no Codex-specific canonical subsystem",
"No direct external mutation outside existing Authority/Commit boundaries",
"Agent-specific instruction is derived adapter context, not canonical human intent",
"Codex bridge may use only the least-privilege runtime/credential path required for bounded repository work",
"Docker-test remains prohibited"
],
"persistence_and_migrations": [
"owner-local Work and Capability schema extensions only",
"no cross-owner FK",
"no cross-owner SQL join dependency",
"result evidence stored by typed refs/metadata rather than payload duplication where possible"
],
"failure_retry_restart": [
"execution-window exhaustion is resumable and not automatic work failure",
"technical agent failure is distinct from human-dependent blocker",
"agent self-report is insufficient for Work completion without verification evidence",
"Codex unavailable must create capability unavailability/gap rather than startup failure"
],
"assumptions": [
"DEV-000068 production foundation is active",
"CapabilityImplementation(kind=AGENT) is sufficient for a general agent descriptor V1",
"Work is the minimal correct owner for durable delegation lifecycle",
"Continuity attention/human-response foundations from DEV-000066 can be reused for delegation waiting/blockers"
],
"acceptance_scenarios": [
"software-engineering requirement resolves through executor-neutral capability resolution",
"Codex is represented as an implementation, not canonical capability identity",
"delegation run checkpoints and resumes after execution-window exhaustion",
"human-dependent blocker surfaces through Attention while technical failure does not masquerade as human blocker",
"agent-specific execution instruction is derived from canonical context and OriginalHumanIntent remains unchanged",
"safe real self-development scenario creates delegation, run, checkpoint/result evidence and ExperienceRecord",
"LLM-free system starts when Codex bridge is unavailable"
],
"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",
"delegation/resume/attention/provider-neutral contract tests",
"production health",
"safe live Codex delegation acceptance"
],
"terminal_gap_states": [
"BLOCKED_BY_ARCHITECTURE_GAP",
"BLOCKED_BY_SPEC_GAP",
"BLOCKED_BY_MISSING_CAPABILITY",
"BLOCKED_BY_CONFLICT"
]
}
+12 -1
View File
@@ -41,7 +41,7 @@ func InterpretHumanResponse(request contract.HumanResponseRequest, owner identit
Revision: 1,
UserReportedNotVerified: false,
}
if operation == contract.ResponseUnknown || operation == contract.ResponseContinue || operation == contract.ResponseReject {
if operation == contract.ResponseUnknown {
return contract.HumanResponseResult{Response: response, Handled: false}, nil
}
if len(candidates) == 0 {
@@ -67,11 +67,22 @@ func InterpretHumanResponse(request contract.HumanResponseRequest, owner identit
response.TargetKind = contract.ResponseTargetAttentionItem
response.TargetAttentionID = candidate.Attention.ID
response.TargetProspectiveID = candidate.Attention.SourceProspectiveID
response.TargetExternalRef = candidate.Attention.SourceExternalRef
} else {
response.TargetKind = contract.ResponseTargetProspectiveItem
response.TargetProspectiveID = candidate.Prospective.ID
}
response.ResolutionState = contract.ResponseResolved
if response.TargetExternalRef != "" && candidate.Attention.SourceKind == contract.AttentionSourceAgentDelegation {
switch operation {
case contract.ResponseContinue:
response.Outcome = contract.ResponseOutcomeContinued
return contract.HumanResponseResult{Response: response, Handled: true, Reply: "Принял. Продолжаю работу по этой задаче."}, nil
case contract.ResponseReject:
response.Outcome = contract.ResponseOutcomeRejected
return contract.HumanResponseResult{Response: response, Handled: true, Reply: "Понял. Зафиксировал, что здесь нужно другое решение."}, nil
}
}
switch operation {
case contract.ResponseComplete:
response.Outcome = contract.ResponseOutcomeUserReportedComplete
@@ -87,6 +87,42 @@ func TestInterpretHumanResponseNoTargetAsksClarification(t *testing.T) {
}
}
func TestInterpretHumanResponseContinuesAgentDelegationAttention(t *testing.T) {
result := interpretResponseForTest(t, "Продолжай", []ResponseCandidate{{
Attention: continuity.AttentionItem{
ID: "attention-1",
OwnerPrincipalID: "principal-1",
ScopeID: "scope-1",
SourceKind: continuity.AttentionSourceAgentDelegation,
SourceExternalRef: "work:delegation:delegation-1",
State: continuity.AttentionDelivered,
Summary: "нужен твой выбор по задаче coding-agent",
Revision: 1,
},
}})
if !result.Handled || result.Response.Operation != continuity.ResponseContinue || result.Response.Outcome != continuity.ResponseOutcomeContinued || result.Response.TargetExternalRef != "work:delegation:delegation-1" {
t.Fatalf("unexpected delegation continue response: %#v", result)
}
}
func TestInterpretHumanResponseRejectsAgentDelegationAttention(t *testing.T) {
result := interpretResponseForTest(t, "Нет, нужен другой вариант", []ResponseCandidate{{
Attention: continuity.AttentionItem{
ID: "attention-1",
OwnerPrincipalID: "principal-1",
ScopeID: "scope-1",
SourceKind: continuity.AttentionSourceAgentDelegation,
SourceExternalRef: "work:delegation:delegation-1",
State: continuity.AttentionDelivered,
Summary: "нужен твой выбор по задаче coding-agent",
Revision: 1,
},
}})
if !result.Handled || result.Response.Operation != continuity.ResponseReject || result.Response.Outcome != continuity.ResponseOutcomeRejected {
t.Fatalf("unexpected delegation reject response: %#v", result)
}
}
func interpretResponseForTest(t *testing.T, message string, candidates []ResponseCandidate) continuity.HumanResponseResult {
t.Helper()
result, err := InterpretHumanResponse(continuity.HumanResponseRequest{
+326
View File
@@ -0,0 +1,326 @@
package domain
import (
"errors"
"strings"
"time"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/work"
"git.cin.su/m/Assistant/platform/ids"
)
var ErrInvalidDelegation = errors.New("invalid delegation")
var ErrInvalidAgentRun = errors.New("invalid agent run")
var ErrInvalidAgentCheckpoint = errors.New("invalid agent checkpoint")
var ErrInvalidAgentResult = errors.New("invalid agent result")
var ErrInvalidDelegationVerification = errors.New("invalid delegation verification")
type DelegationRequest struct {
Goal work.Goal
Plan work.WorkPlan
Candidate work.PlanCandidate
Requirement work.CapabilityRequirement
Implementation capability.CapabilityImplementation
ConversationID work.ConversationRef
OriginalIntentText string
ContextSummary string
ContextRefs []string
VerificationSummary string
DerivedInstruction string
StartedAt time.Time
}
type DelegationBundle struct {
Delegation work.Delegation
Run work.AgentRun
}
func StartDelegation(input DelegationRequest) (DelegationBundle, error) {
if input.Goal.ID == "" || input.Plan.ID == "" || input.Candidate.ID == "" || input.Requirement.ID == "" ||
input.Implementation.ID == "" || input.Implementation.Kind != capability.ImplementationAgent ||
input.Requirement.CapabilityKind == "" || input.StartedAt.IsZero() || strings.TrimSpace(input.OriginalIntentText) == "" {
return DelegationBundle{}, ErrInvalidDelegation
}
delegationID, err := ids.New()
if err != nil {
return DelegationBundle{}, err
}
runID, err := ids.New()
if err != nil {
return DelegationBundle{}, err
}
millis := input.StartedAt.UnixMilli()
delegation := work.Delegation{
ID: work.DelegationID(delegationID),
GoalID: input.Goal.ID,
PlanID: input.Plan.ID,
PlanCandidateID: input.Candidate.ID,
CapabilityRequirementID: input.Requirement.ID,
CapabilityKind: input.Requirement.CapabilityKind,
SelectedImplementationID: input.Implementation.ID,
SelectedImplementationRev: input.Implementation.Revision,
SelectedImplementationKind: input.Implementation.Kind,
Protocol: protocolForImplementation(input.Implementation),
SourceIntentID: input.Goal.SourceIntentID,
SourceUtteranceID: input.Goal.SourceUtteranceID,
ConversationID: input.ConversationID,
ProjectID: input.Goal.ProjectID,
ProjectDisplayName: input.Goal.ProjectDisplayName,
DesiredOutcome: input.Goal.DesiredOutcome,
ConstraintSummary: nonEmpty(input.Goal.Constraints, "no additional constraints"),
VerificationSummary: nonEmpty(input.VerificationSummary, "verify outcome through tests, repository state, and explicit evidence"),
ContextSummary: buildContextSummary(input),
ContextRefs: append([]string{}, input.ContextRefs...),
State: work.DelegationRunning,
LatestRunID: work.AgentRunID(runID),
CreatedAtUnixMillis: millis,
UpdatedAtUnixMillis: millis,
}
run := work.AgentRun{
ID: work.AgentRunID(runID),
DelegationID: delegation.ID,
ImplementationID: input.Implementation.ID,
ImplementationRevision: input.Implementation.Revision,
Protocol: delegation.Protocol,
State: work.AgentRunStarted,
DerivedInstructionSummary: summarizeInstruction(input.DerivedInstruction),
StartedAtUnixMillis: millis,
UpdatedAtUnixMillis: millis,
}
if err := ValidateDelegation(delegation); err != nil {
return DelegationBundle{}, err
}
if err := ValidateAgentRun(run); err != nil {
return DelegationBundle{}, err
}
return DelegationBundle{Delegation: delegation, Run: run}, nil
}
func ValidateDelegation(value work.Delegation) error {
if value.ID == "" || value.GoalID == "" || value.PlanID == "" || value.PlanCandidateID == "" ||
value.CapabilityRequirementID == "" || value.CapabilityKind == "" || value.SelectedImplementationID == "" ||
value.SelectedImplementationRev < 1 || value.SelectedImplementationKind != capability.ImplementationAgent ||
value.Protocol == "" || value.SourceIntentID == "" || value.SourceUtteranceID == "" ||
strings.TrimSpace(value.DesiredOutcome) == "" || value.State == "" || value.CreatedAtUnixMillis < 1 ||
value.UpdatedAtUnixMillis < value.CreatedAtUnixMillis {
return ErrInvalidDelegation
}
return nil
}
func ValidateAgentRun(value work.AgentRun) error {
if value.ID == "" || value.DelegationID == "" || value.ImplementationID == "" || value.ImplementationRevision < 1 ||
value.Protocol == "" || value.State == "" || value.StartedAtUnixMillis < 1 || value.UpdatedAtUnixMillis < value.StartedAtUnixMillis {
return ErrInvalidAgentRun
}
return nil
}
func CheckpointRun(run work.AgentRun, blocker work.AgentBlockerClass, completed, remaining, tests, branch, commit, next string, refs []work.EvidenceRef, now time.Time) (work.AgentRun, work.AgentCheckpoint, error) {
if err := ValidateAgentRun(run); err != nil || now.IsZero() {
return work.AgentRun{}, work.AgentCheckpoint{}, ErrInvalidAgentRun
}
checkpointID, err := ids.New()
if err != nil {
return work.AgentRun{}, work.AgentCheckpoint{}, err
}
state := work.AgentRunCheckpointed
switch blocker {
case work.AgentBlockerExecutionWindow:
state = work.AgentRunExecutionWindowExhausted
case work.AgentBlockerHumanDecisionRequired, work.AgentBlockerMissingExternalCredential, work.AgentBlockerArchitectureDecision, work.AgentBlockerSecurityConflict:
state = work.AgentRunBlocked
case work.AgentBlockerTechnicalFailure:
state = work.AgentRunFailed
case work.AgentBlockerNone:
state = work.AgentRunCheckpointed
default:
return work.AgentRun{}, work.AgentCheckpoint{}, ErrInvalidAgentCheckpoint
}
run.State = state
run.UpdatedAtUnixMillis = now.UnixMilli()
checkpoint := work.AgentCheckpoint{
ID: work.AgentCheckpointID(checkpointID),
DelegationID: run.DelegationID,
RunID: run.ID,
State: state,
BlockerClass: blocker,
CompletedSummary: strings.TrimSpace(completed),
RemainingSummary: strings.TrimSpace(remaining),
TestSummary: strings.TrimSpace(tests),
BranchName: strings.TrimSpace(branch),
CommitHash: strings.TrimSpace(commit),
NextStep: strings.TrimSpace(next),
ArtifactRefs: append([]work.EvidenceRef{}, refs...),
RecordedAtUnixMillis: now.UnixMilli(),
}
if err := ValidateAgentCheckpoint(checkpoint); err != nil {
return work.AgentRun{}, work.AgentCheckpoint{}, err
}
return run, checkpoint, nil
}
func ValidateAgentCheckpoint(value work.AgentCheckpoint) error {
if value.ID == "" || value.DelegationID == "" || value.RunID == "" || value.State == "" || value.BlockerClass == "" || value.RecordedAtUnixMillis < 1 {
return ErrInvalidAgentCheckpoint
}
return nil
}
func CompleteRun(run work.AgentRun, summary, outcomeRef, verification string, refs []work.EvidenceRef, now time.Time) (work.AgentRun, work.AgentResult, error) {
if err := ValidateAgentRun(run); err != nil || now.IsZero() || strings.TrimSpace(summary) == "" {
return work.AgentRun{}, work.AgentResult{}, ErrInvalidAgentResult
}
resultID, err := ids.New()
if err != nil {
return work.AgentRun{}, work.AgentResult{}, err
}
run.State = work.AgentRunCompleted
run.UpdatedAtUnixMillis = now.UnixMilli()
run.CompletedAtUnixMillis = now.UnixMilli()
result := work.AgentResult{
ID: work.AgentResultID(resultID),
DelegationID: run.DelegationID,
RunID: run.ID,
State: work.AgentRunCompleted,
Summary: strings.TrimSpace(summary),
OutcomeRef: strings.TrimSpace(outcomeRef),
VerificationRequired: true,
VerificationSummary: nonEmpty(verification, "verify result before higher-level work completion"),
ArtifactRefs: append([]work.EvidenceRef{}, refs...),
RecordedAtUnixMillis: now.UnixMilli(),
}
if err := ValidateAgentResult(result); err != nil {
return work.AgentRun{}, work.AgentResult{}, err
}
return run, result, nil
}
func ValidateAgentResult(value work.AgentResult) error {
if value.ID == "" || value.DelegationID == "" || value.RunID == "" || value.State == "" ||
strings.TrimSpace(value.Summary) == "" || value.RecordedAtUnixMillis < 1 {
return ErrInvalidAgentResult
}
return nil
}
func ResumeDelegationRun(delegation work.Delegation, previous work.AgentRun, checkpoint work.AgentCheckpoint, now time.Time) (work.Delegation, work.AgentRun, error) {
if err := ValidateDelegation(delegation); err != nil {
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
}
if err := ValidateAgentRun(previous); err != nil {
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
}
if err := ValidateAgentCheckpoint(checkpoint); err != nil || now.IsZero() {
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
}
if delegation.ID != previous.DelegationID || checkpoint.RunID != previous.ID {
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
}
if previous.State != work.AgentRunExecutionWindowExhausted && previous.State != work.AgentRunBlocked && previous.State != work.AgentRunCheckpointed {
return work.Delegation{}, work.AgentRun{}, ErrInvalidAgentRun
}
runID, err := ids.New()
if err != nil {
return work.Delegation{}, work.AgentRun{}, err
}
delegation.State = work.DelegationRunning
delegation.UpdatedAtUnixMillis = now.UnixMilli()
delegation.LatestRunID = work.AgentRunID(runID)
run := work.AgentRun{
ID: work.AgentRunID(runID),
DelegationID: delegation.ID,
ImplementationID: previous.ImplementationID,
ImplementationRevision: previous.ImplementationRevision,
Protocol: previous.Protocol,
State: work.AgentRunStarted,
DerivedInstructionSummary: summarizeInstruction(checkpoint.NextStep),
StartedAtUnixMillis: now.UnixMilli(),
UpdatedAtUnixMillis: now.UnixMilli(),
}
if err := ValidateAgentRun(run); err != nil {
return work.Delegation{}, work.AgentRun{}, err
}
return delegation, run, nil
}
func VerifyDelegationResult(delegation work.Delegation, run work.AgentRun, result work.AgentResult, verified bool, summary string, refs []work.EvidenceRef, now time.Time) (work.Delegation, work.DelegationVerification, error) {
if err := ValidateDelegation(delegation); err != nil {
return work.Delegation{}, work.DelegationVerification{}, ErrInvalidDelegationVerification
}
if err := ValidateAgentRun(run); err != nil {
return work.Delegation{}, work.DelegationVerification{}, ErrInvalidDelegationVerification
}
if err := ValidateAgentResult(result); err != nil || now.IsZero() || strings.TrimSpace(summary) == "" {
return work.Delegation{}, work.DelegationVerification{}, ErrInvalidDelegationVerification
}
verificationID, err := ids.New()
if err != nil {
return work.Delegation{}, work.DelegationVerification{}, err
}
if verified {
delegation.State = work.DelegationCompleted
} else {
delegation.State = work.DelegationFailed
}
delegation.UpdatedAtUnixMillis = now.UnixMilli()
verification := work.DelegationVerification{
ID: work.DelegationVerificationID(verificationID),
DelegationID: delegation.ID,
RunID: run.ID,
ResultID: result.ID,
Verified: verified,
Summary: strings.TrimSpace(summary),
EvidenceRefs: append([]work.EvidenceRef{}, refs...),
RecordedAtUnixMillis: now.UnixMilli(),
}
if err := ValidateDelegationVerification(verification); err != nil {
return work.Delegation{}, work.DelegationVerification{}, err
}
return delegation, verification, nil
}
func ValidateDelegationVerification(value work.DelegationVerification) error {
if value.ID == "" || value.DelegationID == "" || value.RunID == "" || value.ResultID == "" || strings.TrimSpace(value.Summary) == "" || value.RecordedAtUnixMillis < 1 {
return ErrInvalidDelegationVerification
}
return nil
}
func protocolForImplementation(value capability.CapabilityImplementation) work.AgentExecutionProtocol {
if strings.HasPrefix(string(value.TargetRef), "codex://") {
return work.AgentProtocolCodexBridgeV1
}
return work.AgentProtocolCodexBridgeV1
}
func buildContextSummary(input DelegationRequest) string {
parts := []string{}
if input.Goal.ProjectDisplayName != "" {
parts = append(parts, "project="+input.Goal.ProjectDisplayName)
}
parts = append(parts, "human_intent="+strings.TrimSpace(input.OriginalIntentText))
if text := strings.TrimSpace(input.ContextSummary); text != "" {
parts = append(parts, "added_context="+text)
}
return strings.Join(parts, "; ")
}
func summarizeInstruction(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return "derived execution context prepared from canonical intent, project context, and verification requirements"
}
if len(value) > 500 {
return value[:500]
}
return value
}
func nonEmpty(value, fallback string) string {
if strings.TrimSpace(value) == "" {
return fallback
}
return strings.TrimSpace(value)
}
+115
View File
@@ -0,0 +1,115 @@
package domain
import (
"testing"
"time"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/work"
)
func TestStartDelegationPreservesCanonicalIntentAndUsesAgentImplementation(t *testing.T) {
now := time.Unix(170, 0)
bundle, err := StartDelegation(DelegationRequest{
Goal: work.Goal{
ID: "goal-1",
SourceIntentID: "intent-1",
SourceUtteranceID: "utterance-1",
ProjectID: "assistant",
ProjectDisplayName: "Assistant",
DesiredOutcome: "fix the human-facing chat wording",
Constraints: "follow architecture constitution",
},
Plan: work.WorkPlan{ID: "plan-1"},
Candidate: work.PlanCandidate{ID: "candidate-1"},
Requirement: work.CapabilityRequirement{ID: "requirement-1", CapabilityKind: capability.CapabilityKindModifyCode},
Implementation: agentImplementation("codex-impl-1", "codex://assistant"),
ConversationID: "conversation-1",
OriginalIntentText: "Исправь текст в чате Assistant.",
ContextSummary: "relevant files: creator-web chat surface",
ContextRefs: []string{"interaction:intent:intent-1", "situation:project:assistant"},
VerificationSummary: "run targeted tests and verify reply text",
DerivedInstruction: "Open the current branch, inspect chat surface, implement minimal wording fix, run tests.",
StartedAt: now,
})
if err != nil {
t.Fatalf("start delegation: %v", err)
}
if bundle.Delegation.SelectedImplementationKind != capability.ImplementationAgent {
t.Fatalf("delegation must keep AGENT implementation kind: %#v", bundle.Delegation)
}
if bundle.Delegation.SourceIntentID != "intent-1" || bundle.Delegation.SourceUtteranceID != "utterance-1" {
t.Fatalf("canonical human refs lost: %#v", bundle.Delegation)
}
if bundle.Run.Protocol != work.AgentProtocolCodexBridgeV1 {
t.Fatalf("expected codex bridge protocol, got %#v", bundle.Run.Protocol)
}
if bundle.Run.DerivedInstructionSummary == "" {
t.Fatalf("derived instruction summary must be present")
}
}
func TestCheckpointExecutionWindowExhaustionIsNotFailure(t *testing.T) {
run := work.AgentRun{
ID: "run-1",
DelegationID: "delegation-1",
ImplementationID: "impl-1",
ImplementationRevision: 1,
Protocol: work.AgentProtocolCodexBridgeV1,
State: work.AgentRunRunning,
StartedAtUnixMillis: 1,
UpdatedAtUnixMillis: 1,
}
updated, checkpoint, err := CheckpointRun(run, work.AgentBlockerExecutionWindow, "inspected files", "apply patch and run tests", "go test pending", "codex/dev-000069", "abc123", "continue with next execution window", []work.EvidenceRef{"artifact:diff:1"}, time.Unix(200, 0))
if err != nil {
t.Fatalf("checkpoint: %v", err)
}
if updated.State != work.AgentRunExecutionWindowExhausted {
t.Fatalf("execution window exhaustion must be explicit state, got %#v", updated.State)
}
if checkpoint.BlockerClass != work.AgentBlockerExecutionWindow {
t.Fatalf("checkpoint blocker mismatch: %#v", checkpoint)
}
}
func TestDifferentFutureAgentStillSatisfiesSameCapabilityContract(t *testing.T) {
now := time.Unix(170, 0)
for _, implementation := range []capability.CapabilityImplementation{
agentImplementation("codex-impl-1", "codex://assistant"),
agentImplementation("future-impl-1", "research-agent://cluster-a"),
} {
bundle, err := StartDelegation(DelegationRequest{
Goal: work.Goal{ID: "goal-1", SourceIntentID: "intent-1", SourceUtteranceID: "utterance-1", DesiredOutcome: "do the work"},
Plan: work.WorkPlan{ID: "plan-1"},
Candidate: work.PlanCandidate{ID: "candidate-1"},
Requirement: work.CapabilityRequirement{ID: "requirement-1", CapabilityKind: capability.CapabilityKindModifyCode},
Implementation: implementation,
OriginalIntentText: "Сделай безопасное изменение.",
StartedAt: now,
})
if err != nil {
t.Fatalf("start delegation for %s: %v", implementation.ID, err)
}
if bundle.Delegation.CapabilityKind != capability.CapabilityKindModifyCode {
t.Fatalf("capability contract changed for %s: %#v", implementation.ID, bundle.Delegation)
}
}
}
func agentImplementation(id, target string) capability.CapabilityImplementation {
return capability.CapabilityImplementation{
ID: capability.CapabilityImplementationID(id),
CapabilityID: "modify-code",
CapabilityKind: capability.CapabilityKindModifyCode,
OwnerPrincipalID: "domovoy-creator-principal",
Revision: 1,
Kind: capability.ImplementationAgent,
TargetRef: capability.ImplementationTargetRef(target),
Qualification: capability.QualificationValidated,
Activation: capability.ActivationCanary,
Health: capability.HealthAvailable,
AuthorityPolicyRef: capability.AuthorityPolicyCreatorDirectiveV1,
DataConstraints: []capability.DataConstraint{capability.DataConstraintLocalOnly, capability.DataConstraintNoCredentialForward},
ObservedAtUnixMillis: 1,
}
}