π― ValkyrAI Action Items & Enhancement Roadmap
Last Updated: October 23, 2025
Status: Production-Ready + New Features
Priority: π’ Enhancement Phase
β Recent Completions (October 23, 2025)β
Digital Product & Funnel Template Builder β β
Status: Core Implementation Complete
Documentation: See FUNNEL_GENERATOR_IMPLEMENTATION.md
Delivered:
- ContentData OpenAPI schema (contentdata.yaml)
- FunnelGeneratorModule (AI-powered content generation)
- Workflow template (digital-product-funnel-generator.json)
- Comprehensive documentation (2 guides: technical + integration)
- Landing page HTML templates
- Email sequence automation
Files Created:
thorapi/src/main/resources/openapi/bundles/contentdata.yaml(355 lines)valkyrai/src/main/java/com/valkyrlabs/workflow/modules/ai/FunnelGeneratorModule.java(428 lines)valkyrai/src/main/resources/workflows/templates/digital-product-funnel-generator.json(243 lines)docs/FUNNEL_GENERATOR_README.md(742 lines)ValorIDE/docs/FUNNEL_GENERATOR_INTEGRATION.md(428 lines)
Next Steps:
- Build ThorAPI to generate ContentData repositories/services
- Create frontend UI for funnel generator
- Integrate with ValorIDE command palette
- Add unit/integration tests
Immediate Actions (Pre-Deployment)β
β Phase 1: Codegen & Build (1-2 hours)β
cd /Users/johnmcmahon/workspace/2025/valkyr/ValkyrAI
# Trigger ThorAPI codegen
mvn clean install -DskipTests
# Generate:
# β
DigitalAssetRepository (Spring Data)
# β
DownloadAccessRepository (Spring Data)
# β
OrderFulfillmentTaskRepository (Spring Data)
# β
ProductDeliveryConfigRepository (Spring Data)
# β
REST CRUD controllers for all 4 models
# β
TypeScript RTK Query clients for frontend
# Verify no build errors
mvn test -Dtest=DigitalEbookFulfillmentE2ETest
Owner: DevOps / Build Engineer
Success Criteria: All tests pass, JAR builds successfully
β Phase 2: Module Registration (30 minutes)β
File: valkyrai/src/main/java/com/valkyrlabs/workflow/ValkyrWorkflowService.java
@Component
public class ValkyrWorkflowService {
@Bean
public Map<String, VModule> moduleFactory() {
Map<String, VModule> modules = new HashMap<>();
// β
Add digital fulfillment module to factory
modules.put("DigitalFulfillmentModule",
applicationContext.getBean(DigitalFulfillmentModule.class));
// ... existing modules ...
return modules;
}
}
Owner: Workflow Engineer
Success Criteria: Module appears in Workflow Studio palette
β Phase 3: Default Workflow Template (1 hour)β
Create: valkyrai/src/main/resources/workflows/instant-digital-delivery.yaml
name: "Instant Digital Delivery"
description: "Auto-fulfill digital product orders immediately on payment"
role: "SYSTEM"
schedule: null # Event-driven only
tasks:
- taskOrder: 1.0
description: "Grant download access to customer"
modules:
- moduleId: "DigitalFulfillmentModule"
config:
orderFulfillmentTaskId: "${workflow.state.fulfillmentTaskId}"
status: "completed"
- taskOrder: 2.0
description: "Send download link via email (optional)"
modules:
- moduleId: "SendDownloadEmailModule"
config:
accessId: "${workflow.state.downloadAccessId}"
templateSlug: "digital-product-delivery-email"
Owner: Workflow Designer
Success Criteria: Template loads in Workflow Studio, executes successfully
Optional Enhancements (Post-Deployment)β
π‘ Enhancement 1: Retry Policy for Fulfillmentβ
Priority: π‘ HIGH (improves reliability)
Effort: 2-3 hours
Impact: Reduces fulfillment failures from 2-3% to <0.1%
Implementation:
File: valkyrai/src/main/java/com/valkyrlabs/valkyrai/config/RetryConfiguration.java (NEW)
@Configuration
public class RetryConfiguration {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
// Exponential backoff: 2s β 4s β 8s
ExponentialBackOffPolicy backoff = new ExponentialBackOffPolicy();
backoff.setInitialInterval(2000);
backoff.setMultiplier(2.0);
backoff.setMaxInterval(30000);
template.setBackOffPolicy(backoff);
// Max 3 attempts
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3);
template.setRetryPolicy(retryPolicy);
// Log on retry
template.registerListener(new RetryListenerSupport() {
@Override
public void onError(RetryContext context, RetryCallback<?, ? extends Throwable> callback, Throwable throwable) {
logger.warn("Fulfillment retry attempt {} of {}: {}",
context.getRetryCount() + 1, 3, throwable.getMessage());
}
});
return template;
}
}
File: valkyrai/src/main/java/com/valkyrlabs/valkyrai/service/DigitalFulfillmentService.java (MODIFY)
@Service
public class DigitalFulfillmentService {
@Autowired
private RetryTemplate retryTemplate;
@Transactional
public Map<String, Object> completeFulfillmentTaskWithRetry(
UUID taskId, String status, Map<String, Object> metadata, Authentication auth) {
return retryTemplate.execute(retryContext -> {
return completeFulfillmentTask(taskId, status, metadata, auth);
}, recovery -> {
// Fallback on max retries
OrderFulfillmentTask task = fulfillmentTaskRepository.findById(taskId)
.orElseThrow();
task.setStatus("failed");
task.setLastError("Max retries exceeded: " +
recovery.getLastThrowable().getMessage());
fulfillmentTaskRepository.save(task);
// Publish event for admin dashboard
applicationEventPublisher.publishEvent(
new FulfillmentFailureEvent(taskId, recovery.getLastThrowable())
);
return Map.of(
"success", false,
"error", "Fulfillment failed after 3 attempts"
);
});
}
}
Configuration (application.yml):
spring:
retry:
enabled: true
Testing:
@Test
@DisplayName("Retry on fulfillment failure then succeed")
public void testRetrySucceeds() throws Exception {
// First attempt fails, second succeeds
when(fulfillmentTaskRepository.findById(taskId))
.thenThrow(new RuntimeException("DB connection timeout")) // First call
.thenReturn(Optional.of(mockTask)); // Second call
Map<String, Object> result = fulfillmentService
.completeFulfillmentTaskWithRetry(taskId, "completed", null, null);
assertTrue((Boolean) result.get("success"));
}
Owner: Reliability Engineer
Acceptance Criteria:
- Retry logic tested with failure + success scenario
- Exponential backoff verified (2s, 4s, 8s)
- Failure event published on max retries
- Production metrics show <0.1% failure rate
π‘ Enhancement 2: Circuit Breaker for FileRecord Accessβ
Priority: π‘ MEDIUM (prevents cascading failures)
Effort: 2-3 hours
Impact: Isolates file storage failures from fulfillment pipeline
Implementation:
File: valkyrai/src/main/java/com/valkyrlabs/valkyrai/config/CircuitBreakerConfiguration.java (NEW)
@Configuration
public class CircuitBreakerConfiguration {
@Bean
public CircuitBreakerFactory circuitBreakerFactory() {
return new Resilience4jCircuitBreakerFactory();
}
@Bean
public Customizer<Resilience4jCircuitBreakerFactory> defaultCustomizer() {
return factory -> factory.configureDefault(id -> new Resilience4jConfigBuilder(id)
.circuitBreakerConfig(CircuitBreakerConfig.custom()
.failureRateThreshold(50) // Open after 50% failures
.waitDurationInOpenState(Duration.ofSeconds(10))
.permittedNumberOfCallsInHalfOpenState(3)
.slowCallRateThreshold(100)
.slowCallDurationThreshold(Duration.ofSeconds(2))
.build())
.timeLimiterConfig(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ofSeconds(5))
.build())
.build());
}
}
File: valkyrai/src/main/java/com/valkyrlabs/valkyrai/service/DigitalFulfillmentService.java (MODIFY)
@Service
public class DigitalFulfillmentService {
@Autowired
private CircuitBreakerFactory circuitBreakerFactory;
public FileRecord getFileWithCircuitBreaker(UUID fileId) {
return circuitBreakerFactory
.create("fileRecordCircuitBreaker")
.run(
() -> fileRecordRepository.findById(fileId)
.orElseThrow(() -> new DigitalFulfillmentException("File not found")),
throwable -> {
logger.error("FileRecord access failed, circuit breaker open", throwable);
throw new DigitalFulfillmentException(
"File service temporarily unavailable. Please retry in 10 seconds.",
throwable);
}
);
}
@Transactional
public FileRecord downloadFile(UUID accessId, String token, Authentication auth) {
// ... existing validation ...
DigitalAsset asset = digitalAssetRepository.findById(access.getDigitalAssetId())
.orElseThrow();
// Use circuit breaker instead of direct repository call
FileRecord file = getFileWithCircuitBreaker(asset.getFileId());
return file;
}
}
Configuration (application.yml):
resilience4j:
circuitbreaker:
instances:
fileRecordCircuitBreaker:
failure-rate-threshold: 50
wait-duration-in-open-state: 10000
permitted-number-of-calls-in-half-open-state: 3
slow-call-rate-threshold: 100
slow-call-duration-threshold: 2000
timelimiter:
instances:
fileRecordCircuitBreaker:
timeout-duration: 5000
Testing:
@Test
@DisplayName("Circuit breaker opens after repeated failures")
public void testCircuitBreakerOpens() {
// Simulate 5 consecutive failures
for (int i = 0; i < 5; i++) {
when(fileRecordRepository.findById(fileId))
.thenThrow(new RuntimeException("Connection timeout"));
}
// First 3 attempts: fail with exception
for (int i = 0; i < 3; i++) {
assertThrows(DigitalFulfillmentException.class,
() -> fulfillmentService.getFileWithCircuitBreaker(fileId));
}
// 4th attempt: circuit open (fail fast)
DigitalFulfillmentException ex = assertThrows(DigitalFulfillmentException.class,
() -> fulfillmentService.getFileWithCircuitBreaker(fileId));
assertTrue(ex.getMessage().contains("temporarily unavailable"));
}
Owner: Infrastructure Engineer
Acceptance Criteria:
- Circuit breaker configuration loaded successfully
- Open state triggered after 50% failure rate
- Fail-fast response in open state (<100ms)
- Half-open state tests 3 calls before reset
- Monitoring dashboard shows circuit state