diff --git a/sdk/go/README.md b/sdk/go/README.md index 53442f00..133b26c2 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -46,6 +46,40 @@ func main() { - `types`: Shared data structures and contracts. - `ai`: Helpers for interacting with AI providers via the control plane. +## Human-in-the-Loop Approvals + +The `client` package provides methods for requesting human approval, checking status, and waiting for decisions: + +```go +import "github.com/Agent-Field/agentfield/sdk/go/client" + +approvalClient := client.New("http://localhost:8080", nil) + +// Request approval — transitions execution to "waiting" +_, err := approvalClient.RequestApproval(ctx, nodeID, executionID, + client.RequestApprovalRequest{ + Title: "Review Deployment", + ProjectID: "my-project", + TemplateType: "plan-review-v1", + ExpiresInHours: 24, + }, +) + +// Wait for human decision (uses context.Context for timeout) +waitCtx, cancel := context.WithTimeout(ctx, 1*time.Hour) +defer cancel() + +result, err := approvalClient.WaitForApproval(waitCtx, nodeID, executionID, + &client.WaitForApprovalOptions{ + PollInterval: 5 * time.Second, + MaxInterval: 30 * time.Second, + }, +) +// result.Status is "approved", "rejected", or "expired" +``` + +**Methods:** `RequestApproval()`, `GetApprovalStatus()`, `WaitForApproval()` + ## Testing ```bash diff --git a/sdk/python/README.md b/sdk/python/README.md index 940605bf..9becf294 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -39,6 +39,41 @@ if __name__ == "__main__": agent.serve(port=8001) ``` +## Human-in-the-Loop Approvals + +The Python SDK provides a first-class waiting state for pausing agent execution mid-reasoner and waiting for human approval: + +```python +from agentfield import Agent, ApprovalResult + +app = Agent(node_id="reviewer", agentfield_server="http://localhost:8080") + +@app.reasoner() +async def deploy(environment: str) -> dict: + plan = await app.ai(f"Create deployment plan for {environment}") + + # Pause execution and wait for human approval + result: ApprovalResult = await app.pause( + approval_request_id="req-abc123", + expires_in_hours=24, + timeout=3600, + ) + + if result.approved: + return {"status": "deploying", "plan": str(plan)} + elif result.changes_requested: + return {"status": "revising", "feedback": result.feedback} + else: + return {"status": result.decision} +``` + +**Two API levels:** + +- **High-level:** `app.pause()` blocks the reasoner until approval resolves, with automatic webhook registration +- **Low-level:** `client.request_approval()`, `client.get_approval_status()`, `client.wait_for_approval()` for fine-grained control + +See `examples/python_agent_nodes/waiting_state/` for a complete working example. + See `docs/DEVELOPMENT.md` for instructions on wiring agents to the control plane. ## Testing diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index fb302e27..0585654a 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -53,3 +53,41 @@ agent.reasoner('process', async (ctx) => { ``` **Use `note()` for AgentField UI tracking, `console.log()` for local debugging.** + +## Human-in-the-Loop Approvals + +Use the `ApprovalClient` to pause agent execution for human review: + +```ts +import { Agent, ApprovalClient } from '@agentfield/sdk'; + +const agent = new Agent({ nodeId: 'reviewer', agentFieldUrl: 'http://localhost:8080' }); +const approvalClient = new ApprovalClient({ + baseURL: 'http://localhost:8080', + nodeId: 'reviewer', +}); + +agent.reasoner<{ task: string }, { status: string }>('deploy', async (ctx) => { + const plan = await ctx.ai(`Create deployment plan for: ${ctx.input.task}`); + + // Request approval — transitions execution to "waiting" + await approvalClient.requestApproval(ctx.executionId, { + projectId: 'my-project', + title: `Deploy: ${ctx.input.task}`, + description: String(plan), + expiresInHours: 24, + }); + + // Wait for human decision (polls with exponential backoff) + const result = await approvalClient.waitForApproval(ctx.executionId, { + pollIntervalMs: 5_000, + timeoutMs: 3_600_000, + }); + + return { status: result.status }; +}); +``` + +**Methods:** `requestApproval()`, `getApprovalStatus()`, `waitForApproval()` + +See `examples/ts-node-examples/waiting-state/` for a complete working example.