How to Handle Errors and Exceptions in PentAGI: A Layered Architecture Guide
TLDR: PentAGI handles errors through a layered architecture where low-level code returns wrapped Go errors using fmt.Errorf with %w, the service layer translates these into centralized HTTP error responses via response.Error, and Gin's Recovery middleware catches panics to prevent server crashes while maintaining structured logging throughout.
Error handling in PentAGI (vxcontrol/pentagi) follows a systematic, production-ready approach that separates low-level operations from API responses. This guide examines the actual implementation patterns found in the repository, demonstrating how the backend maintains consistency across database calls, external service integrations, and HTTP endpoints using a centralized error model.
Understanding PentAGI's Layered Error Handling Architecture
PentAGI implements a four-tier error handling strategy that propagates errors from raw Go errors to structured HTTP responses, ensuring each layer has a single responsibility.
Low-Level Error Wrapping with Context
At the foundation, functions in tools and database packages return standard Go errors with context using fmt.Errorf and the %w verb. This preserves the original error while adding domain-specific context for upstream inspection.
In backend/pkg/tools/traversaal.go, external API failures are wrapped as follows:
return "", fmt.Errorf("failed to search in traversaal: %w", err)
This pattern allows service handlers to use errors.Is() or errors.As() to check for specific underlying error types.
Service Layer Error Translation
REST and GraphQL handlers in the service layer use logger.FromContext(...).WithError(err) to capture structured logs before selecting the appropriate HTTP error code. The response.Error function converts internal errors into standardized API responses using the centralized error definitions.
From backend/pkg/server/services/users.go:
logger.FromContext(c).WithError(err).Errorf("error finding current user")
response.Error(c, response.ErrInternal, err)
Centralized HTTP Error Definitions
All API-level errors are defined in backend/pkg/server/response/errors.go. Each *response.HttpError contains an HTTP status code, a machine-readable code string, and a human-readable message, ensuring consistent responses across all endpoints.
Implementing Error Handling in PentAGI Services
When handling errors in service methods, follow the pattern used in the user service implementation. First check for specific error types like gorm.ErrRecordNotFound, then fall back to generic internal errors.
Complete example from backend/pkg/server/services/users.go:
func (s *UserService) GetCurrentUser(c *gin.Context) {
// Call lower-level code (DB, external service, etc.)
if err := s.db.Take(&user, "id = ?", uid).Error; err != nil {
// Log with structured context
logger.FromContext(c).WithError(err).Errorf("error finding current user")
// Choose the appropriate HttpError
if errors.Is(err, gorm.ErrRecordNotFound) {
response.Error(c, response.ErrUsersNotFound, err) // 404
} else {
response.Error(c, response.ErrInternal, err) // 500
}
return
}
// On success, write a success payload
response.Success(c, http.StatusOK, user)
}
This pattern ensures that database-specific errors are translated into appropriate HTTP status codes (404 for missing records, 500 for system failures) while maintaining the original error context for logging.
Logging and Observability in PentAGI Error Handling
Structured logging ensures errors are traceable across distributed systems. PentAGI uses logger.FromContext to extract request-scoped loggers and attaches error details using WithError, enabling correlation between HTTP requests and backend failures.
Example from backend/pkg/tools/traversaal.go showing contextual fields:
logger.FromContext(c).
WithFields(logrus.Fields{
"tool": "traversaal",
"query": action.Query,
}).
WithError(err).
Error("failed to search in traversaal")
This pattern creates searchable log entries containing the tool name, query parameters, and the original error message, enabling rapid debugging of external service failures in log aggregation systems like Loki or ELK.
Handling Panics and Unexpected Errors in PentAGI
Despite careful error handling, panics can occur from unexpected runtime conditions. PentAGI uses Gin's Recovery middleware to catch any panic that bubbles up to the HTTP layer, preventing the entire server from crashing.
Configuration in backend/pkg/server/router.go:
router.Use(gin.Recovery())
This middleware intercepts panics, logs the stack trace using the configured logger, and returns a generic 500 JSON response to the client. This ensures high availability even when unexpected runtime errors occur in production.
Summary
- Layered architecture: Low-level code returns wrapped Go errors using
fmt.Errorfwith%w, while the service layer translates these into HTTP error responses usingresponse.Error. - Centralized error definitions: All API errors are defined in
backend/pkg/server/response/errors.gowith consistent status codes and machine-readable codes. - Structured logging: Use
logger.FromContext(c).WithError(err)to capture error context and enable distributed tracing. - Panic recovery: Gin's
Recoverymiddleware inbackend/pkg/server/router.goensures panics don't crash the server. - Error inspection: Use
errors.Is()to check for specific error types likegorm.ErrRecordNotFoundwhen mapping to HTTP status codes.
Frequently Asked Questions
How does PentAGI wrap errors from external services?
PentAGI uses Go's standard error wrapping with fmt.Errorf and the %w verb to preserve the original error context. For example, in backend/pkg/tools/traversaal.go, external API failures are wrapped as fmt.Errorf("failed to search in traversaal: %w", err), allowing upstream callers to inspect the error chain using errors.Is or errors.As to detect specific failure modes like network timeouts or authentication failures.
What is the role of the HttpError struct in PentAGI?
The HttpError struct, defined in backend/pkg/server/response/errors.go, serves as the centralized error model for all API responses. It encapsulates an HTTP status code, a machine-readable error code (e.g., "Users.NotFound"), and a human-readable message. This ensures that every endpoint returns a consistent JSON schema containing status, code, and msg fields, allowing API clients to handle errors predictably regardless of which service handler generated the failure.
How does PentAGI handle database record not found errors?
In service handlers like backend/pkg/server/services/users.go, database errors are checked using errors.Is(err, gorm.ErrRecordNotFound). When a record is not found, the service logs the error using structured logging and returns response.ErrUsersNotFound, which maps to HTTP 404. For other database errors (connection failures, constraint violations), it returns response.ErrInternal (HTTP 500), ensuring clients receive semantically appropriate status codes while the server maintains detailed error logs for debugging.
Can I add custom error codes to PentAGI?
Yes, you can add custom error codes by defining new *HttpError instances in backend/pkg/server/response/errors.go using the NewHttpError constructor. Specify the HTTP status code, a unique machine-readable code string (using dot notation like "External.ServiceUnavailable"), and a descriptive message. Once defined, import the error variable in your service handler and use response.Error(c, yourCustomError, originalErr) to return consistent, structured error responses that match PentAGI's existing API contract.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →