feat(bounty): add A2A create_sub_task mechanism
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s

This commit is contained in:
OG T
2026-06-07 23:01:03 +08:00
parent bcfd5917a1
commit 8c69a44b9c
6 changed files with 130 additions and 0 deletions

View File

@@ -29,6 +29,8 @@ model Task {
is_priority Boolean @default(false)
is_private Boolean @default(false)
referred_by_agent String?
parent_task_id String?
created_by_agent String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt

View File

@@ -71,3 +71,30 @@ paths:
responses:
'200':
description: OK
/create_sub_task:
post:
operationId: createSubTask
summary: Delegate task to another agent (A2A)
description: Create a sub-task using your own bounty reward budget.
requestBody:
content:
application/json:
schema:
type: object
required: [parent_task_id, claim_token, title, description, reward_amount, acceptance_criteria]
properties:
parent_task_id:
type: string
claim_token:
type: string
title:
type: string
description:
type: string
reward_amount:
type: integer
acceptance_criteria:
type: object
responses:
'200':
description: OK

View File

@@ -4,6 +4,7 @@ import {
ListOpenTasksRequestSchema,
ClaimTaskRequestSchema,
SubmitSolutionRequestSchema,
CreateSubTaskRequestSchema,
TaskStatus,
JudgeOverallResult
} from "@agent-bounty/contracts";
@@ -684,6 +685,70 @@ export async function POST(request: NextRequest, props: { params: Promise<{ tool
});
}
case "create_sub_task": {
const parsed = CreateSubTaskRequestSchema.parse(body);
const subTask = await prisma.$transaction(async (tx) => {
const claim = await tx.claim.findUnique({ where: { claim_token: parsed.claim_token } });
if (!claim || claim.task_id !== parsed.parent_task_id || claim.status !== TaskStatus.EXECUTING) {
throw new Error("Invalid claim token or parent task is not EXECUTING");
}
if (parsed.reward_amount >= claim.held_amount) {
throw new Error("Sub-task reward cannot exceed parent task reward");
}
const newTask = await tx.task.create({
data: {
title: `[Sub-Task] ${parsed.title}`,
description: parsed.description,
status: TaskStatus.OPEN,
difficulty: "HELLO_WORLD",
reward_amount: parsed.reward_amount,
reward_currency: claim.held_currency,
required_stack: ["A2A", "Agent Sub-Task"],
scope_clarity_score: 1.0,
parent_task_id: parsed.parent_task_id,
created_by_agent: claim.agent_id,
acceptance_criteria: parsed.acceptance_criteria as any,
is_priority: true, // Sub tasks are high priority to finish the main task faster
}
});
await logAuditEvent(tx, {
actorType: "AGENT",
actorId: claim.agent_id,
action: "CREATE_SUB_TASK",
entityType: "TASK",
entityId: newTask.id,
beforeState: null,
afterState: { status: TaskStatus.OPEN, parent: claim.task_id }
});
return newTask;
});
void sendTrafficAlert({
level: "info",
action: "EXTERNAL_CREATE_SUB_TASK_SUCCESS",
surface: "mcp/create_sub_task",
actorType: "AGENT",
actorId: subTask.created_by_agent!,
taskId: subTask.id,
message: `A2A 內循環Agent 發佈了子任務: ${subTask.id}`,
metadata: {
parent_task_id: parsed.parent_task_id,
reward: parsed.reward_amount,
payload_summary: summarizeRequestPayload(tool, body),
}
});
return NextResponse.json({
sub_task_id: subTask.id,
status: subTask.status,
request_id: requestContext.request_id,
});
}
case "check_payout_status": {
const parsed = z.object({ task_id: z.string().uuid() }).parse(body);

View File

@@ -228,5 +228,6 @@ export const MCPToolName = {
CLAIM_TASK: "claim_task",
SUBMIT_SOLUTION: "submit_solution",
CHECK_PAYOUT_STATUS: "check_payout_status",
CREATE_SUB_TASK: "create_sub_task",
} as const;
export type MCPToolName = (typeof MCPToolName)[keyof typeof MCPToolName];

View File

@@ -181,6 +181,27 @@ export const SubmitSolutionResponseSchema = z.object({
estimated_judge_complete_at: z.string().datetime().optional(),
});
// ─────────────────────────────────────────────
// create_sub_task Request/Response
// ─────────────────────────────────────────────
export const CreateSubTaskRequestSchema = z.object({
parent_task_id: UUIDSchema,
claim_token: z.string().uuid(),
title: z.string().min(5).max(120),
description: z.string().min(20).max(2000),
reward_amount: MoneyAmountSchema,
acceptance_criteria: AcceptanceCriteriaSchema,
});
export const CreateSubTaskResponseSchema = z.object({
sub_task_id: UUIDSchema,
status: z.union([
z.literal(TaskStatus.DRAFT),
z.literal(TaskStatus.OPEN),
]),
});
// ─────────────────────────────────────────────
// Judge Result SchemaE2B 沙盒回傳)
// ─────────────────────────────────────────────

View File

@@ -12,6 +12,7 @@ import {
ListOpenTasksRequestSchema,
ClaimTaskRequestSchema,
SubmitSolutionRequestSchema,
CreateSubTaskRequestSchema,
} from "@agent-bounty/contracts";
import { z } from "zod";
@@ -70,6 +71,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
}) as any
),
},
{
name: MCPToolName.CREATE_SUB_TASK,
description: "[A2A Bounties] Delegate a part of your current task to another AI agent by creating a sub-task. The reward will be deducted from your final payout.",
inputSchema: zodToJsonSchema(CreateSubTaskRequestSchema as any),
},
],
};
});
@@ -117,6 +123,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
};
}
case MCPToolName.CREATE_SUB_TASK: {
const parsed = CreateSubTaskRequestSchema.parse(args);
const data = await proxyToBackend("/api/mcp/create_sub_task", parsed, API_BASE_URL, API_KEY);
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}