Skip to content

Conversation

@betterclever
Copy link
Contributor

@betterclever betterclever commented Dec 28, 2025

This PR implements the foundational Human-in-the-Loop (HITL) system for ShipSec AI. It enables workflows to pause execution and wait for human intervention—whether for simple approvals, data collection via forms, or making specific selections.

This is a comprehensive implementation spanning the backend (Temporal, Drizzle, NestJS) and the frontend (Action Center, Workflow Designer).

Key Features

1. Centralized Action Center

  • A new Action Center (/actions) that serves as a command center for all manual tasks.
  • Filter tasks by status (Pending, Resolved, Expired).
  • Search and sort by Workflow Run ID, Node Name, or Title.
  • Direct response actions from the table view for quick approvals.

2. Manual Action Components (HITL Nodes)

Implemented a set of specialized nodes for the workflow designer:

  • Manual Approval: A binary gate (Approve/Reject) to control workflow flow.
  • Manual Form: Generates dynamic UI forms based on configurable JSON Schema. Supports strings, numbers, enums, and booleans.
  • Manual Selection: Allows humans to choose from a list of predefined options (single or multiple choice).
  • Manual Acknowledgment: A "Mark as Read" style node to ensure human awareness before proceeding.

3. Dynamic Context & Templating

  • Variable Injection: Task titles and descriptions can now use dynamic variables (e.g., {{steps.scan.output.vulnerabilities}}) to provide humans with the necessary context to make decisions.
  • Markdown Support: Full Markdown rendering in task descriptions for rich context display.

4. Robust Backend Architecture

  • Temporal Integration: Built using Temporal activities that handle suspension and resumption of workflow execution.
  • Persistence: Detailed tracking of requests in Drizzle ORM, including respondedBy, respondedAt, and full responseData payloads.
  • Timeout Handling: Support for configurable timeouts, allowing workflows to handle cases where humans don't respond in time.

5. Unified Resolution Framework

  • Created HumanInputResolutionView, a "smart" component that handles the entire resolution lifecycle.
  • Seamlessly manages different input types (form, selection, approval) within a consistent, premium UI.
  • Shared across the Action Center and the Workflow Execution Inspector for a unified user experience.

Technical Implementation Details

  • Database: Added human_input_requests table with relational support.
  • API: RESTful endpoints for internal system and frontend consumption.
  • Schema: Leveraging Zod for rigorous DTO validation and OpenAPI generation.
  • State Management: Optimized hooks for real-time status updates and interaction handling.

This PR establishes the core capability of "Human-in-the-Loop" which is essential for secure and reliable AI-driven security workflows.

- Add approval_requests database schema and migration
- Create approval-gate component with parameters for title, description, timeout
- Add Temporal signal definition for approval resolution
- Register component in worker

TODO:
- Modify Temporal workflow to handle approval signals
- Create backend API endpoints for approving/rejecting
- Create frontend UI for viewing and actioning approvals

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
- Add ApprovalsService with CRUD operations for approval requests
- Add ApprovalsController with authenticated endpoints
- Add public approve/reject endpoints with secure tokens
- Add signalApproval method to TemporalService for signaling workflows
- Wire up ApprovalsModule in app.module.ts

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Worker changes:
- Add approval-requests schema for worker DB access
- Create approval activity for creating requests in DB
- Modify workflow to handle approval gates with Temporal signals
- Wire up approval activity in dev worker

Frontend changes:
- Create ApprovalsPage for viewing/actioning approvals
- Add approvals route to App router
- Add Approvals navigation item to sidebar

The approval flow:
1. Approval Gate component detects pending approval
2. Worker creates approval request in database
3. Workflow waits for signal using condition()
4. Human approves/rejects via UI or public link
5. Backend sends signal to Temporal
6. Workflow resumes with approval result

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
- Create approvals.dto.ts with Zod schemas using createZodDto
- Update controller to use proper DTOs with Swagger decorators
- Regenerate OpenAPI spec with approval endpoints
- Regenerate backend-client with approval methods
- Update ApprovalsPage to use api.approvals client methods

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
…node trace emission

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
…omponents

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
…n folder and update category to manual_action

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
… context variables

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
…s for manual actions

- Introduced 'variable-list', 'form-fields', and 'selection-options' parameter types.
- Created 'FormFieldsEditor' and 'SelectionOptionsEditor' for interactive node design.
- Refactored 'ParameterField' to generically support specialized editors.
- Added dynamic variable interpolation ({{variable}}) support for form fields and labels.
- Upgraded Manual Approval, Selection, and Form nodes with interactive Designers.
- Improved editor robustness to handle legacy JSON/string data safely.
- Updated Action Center and Workflow runner to support dynamic ports and resolved data output.

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
…ionView

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
…olutionView

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
@betterclever betterclever force-pushed the betterclever/25b2026/d3feat2 branch from 9a17283 to 07f6064 Compare December 28, 2025 14:35
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 219 to 225
if (!signalReceived) {
// Timeout occurred
console.log(`[Workflow] Human input timeout for ${action.ref}`);
throw new Error(`Human input request timed out for node ${action.ref}`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark timed-out human inputs as expired/cancelled

When the wait times out, the workflow throws immediately but never updates the human_input_requests record, so the request stays pending in the database. That leaves stale items visible in the Action Center and allows users to attempt a resolution that will signal a closed workflow (likely throwing from the backend). Consider calling the cancellation/expiry activity before throwing so the request status is consistent with the timed‑out run.

Useful? React with 👍 / 👎.

Comment on lines 435 to 440
if (request.inputType === 'selection') return selectedOptions.length === 0;
if (request.inputType === 'form') return parsedInputSchema?.required?.some((k: string) => !formValues[k]);
return false;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow required boolean/zero form values

The submit button disables when any required field is falsy. This blocks valid inputs like false for required booleans or 0 for required numbers (both evaluate falsy), so users cannot submit those legitimate values. The check should only treat undefined/empty string as missing, not false or 0.

Useful? React with 👍 / 👎.

@betterclever betterclever changed the title Unified Human-in-the-Loop Interaction System feat: Human-in-the-Loop (HITL) system implementation Dec 28, 2025
@betterclever
Copy link
Contributor Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +45 to +48
async getById(id: string): Promise<HumanInputResponseDto> {
const request = await this.db.query.humanInputRequests.findFirst({
where: eq(humanInputRequestsTable.id, id),
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scope human-input lookups by organization

This lookup only filters by id, so any authenticated caller who learns a request ID can read (and then resolve, via resolve()) another org’s human-input request. In a multi-tenant deployment, that’s a cross-tenant data leak and authorization bypass because the AuthGuard only attaches org context and there’s no organizationId predicate here. Please require org context (as other services do) and add organizationId filtering to list/get/resolve queries.

Useful? React with 👍 / 👎.

Comment on lines 219 to 223
if (!signalReceived) {
// Timeout occurred
console.log(`[Workflow] Human input timeout for ${action.ref}`);
throw new Error(`Human input request timed out for node ${action.ref}`);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark timed-out human inputs as expired

When the timeout elapses, the workflow throws but does not update the human_input_requests row (or call the existing cancel activity), so the request remains pending indefinitely. This leaves the Action Center showing stale pending tasks and allows late resolutions for a workflow that has already failed due to timeout. Consider updating the request to expired/cancelled (and emitting a trace event) before throwing on timeout.

Useful? React with 👍 / 👎.

@betterclever
Copy link
Contributor Author

@codex address that feedback

1 similar comment
@betterclever
Copy link
Contributor Author

@codex address that feedback

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
@betterclever betterclever force-pushed the betterclever/25b2026/d3feat2 branch from cb6d148 to 98bef4c Compare December 28, 2025 14:49
@chatgpt-codex-connector
Copy link

Summary

  • Scoped human input list/get/resolve to organization context in the API/service layer (backend/src/human-inputs/human-inputs.service.ts:25-116, backend/src/human-inputs/human-inputs.controller.ts:20-55).
  • Hardened human input timeout handling to expire on immediate timeouts and log expiration failures (worker/src/temporal/workflows/index.ts:200-234).
  • Allowed required form fields to accept false/0 values by treating only undefined/empty string as missing (frontend/src/components/workflow/HumanInputResolutionView.tsx:433-441).

Testing

  • Not run (not requested).

View task →

@chatgpt-codex-connector
Copy link

Summary

  • Scoped human input list/get/resolve to organization context and required auth org in the backend.
  • Allowed form fields to preserve falsy values (0/false) without being coerced to empty UI values.
  • Emitted a trace failure event when human input requests time out.

Testing

  • ⚠️ Not run (not requested).

Screenshots

  • Not captured (UI not run in this environment).

View task →

…ests

Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
@betterclever betterclever merged commit 5f29d52 into main Dec 28, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants