Major changes: - Add internal/logging package with field constants, context propagation, sensitive data auto-redaction, and per-component log levels - Add worker timeout constants (TimeoutQuickOp, TimeoutHealthCheck, etc.) - Extend SDLC with callback handlers, generate endpoints, and executor - Add new cookbook trees for aeries and slackpath progression - Add skeleton templates for queue, realtime, and microservices - Add worker component template with async job processing - Refactor services and handlers to use new logging infrastructure - Split component.go into component_infra.go and component_listing.go Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
788 lines
25 KiB
Markdown
788 lines
25 KiB
Markdown
# rdev Technical Specification for Patent Disclosure
|
|
|
|
- **Subject:** System and Method for Orchestrating AI Agents Through Deterministic Workflow Classification
|
|
- **Date:** 2026-02-04
|
|
|
|
---
|
|
|
|
## Field of the Invention
|
|
|
|
The present invention relates generally to software development automation and AI agent orchestration, and more particularly to methods and systems for constraining non-deterministic AI agents through deterministic classification while executing those agents in isolated Kubernetes environments.
|
|
|
|
---
|
|
|
|
## Background of the Invention
|
|
|
|
### Technical Problem
|
|
|
|
AI agents (large language model-based systems that can execute code and tools) have emerged as powerful tools for software development. However, their integration into production workflows faces a fundamental tension:
|
|
|
|
1. **Agent Autonomy vs. Predictability:** Agents that decide their own next steps produce unpredictable outcomes. The same agent given the same prompt may take different actions.
|
|
|
|
2. **Workflow Rigidity vs. Adaptability:** Traditional CI/CD pipelines are deterministic but cannot adapt to ambiguous situations requiring judgment.
|
|
|
|
3. **Isolation vs. Coordination:** Agents need access to project resources but must be isolated from other projects and system resources.
|
|
|
|
4. **Human Oversight vs. Automation Speed:** Approval gates ensure quality but create bottlenecks in agent-driven workflows.
|
|
|
|
### Prior Art Limitations
|
|
|
|
**AI Agent Frameworks (AutoGPT, LangChain, CrewAI):** Provide tools for building agents but give agents autonomy to select actions. No mechanism constrains action selection externally.
|
|
|
|
**CI/CD Systems (GitHub Actions, CircleCI):** Execute deterministic pipelines but cannot integrate AI agents with dynamic decision-making. Pipelines are static definitions, not adaptive to current state.
|
|
|
|
**Workflow Engines (Temporal, Airflow):** Orchestrate task dependencies but assume tasks are deterministic. No support for constraining non-deterministic agent behavior.
|
|
|
|
**Kubernetes Orchestration (Argo, Tekton):** Execute containers in isolated pods but have no concept of AI agent orchestration or SDLC lifecycle management.
|
|
|
|
---
|
|
|
|
## Summary of the Invention
|
|
|
|
The present invention provides a system and method for orchestrating AI agents through software development workflows using deterministic classification. In one embodiment, a system comprises:
|
|
|
|
- A phase state machine with 10 development phases and artifact requirements
|
|
- A deterministic classifier with 24 priority-ordered rules evaluating state and outputting actions
|
|
- Isolated execution in Kubernetes pods via kubectl exec
|
|
- Dual-execution module running as both CLI (inside pods) and library (in orchestrator)
|
|
- Composable monorepo templates with skeleton + component architecture
|
|
- Per-project worker coordination with atomic task dequeue
|
|
|
|
The system constrains AI agent action selection by outputting specific instructions from the classifier rather than allowing agents to decide their own actions.
|
|
|
|
---
|
|
|
|
## Detailed Description of Preferred Embodiments
|
|
|
|
### 1. Phase State Machine
|
|
|
|
The fundamental workflow model is a **10-phase state machine** representing software development lifecycle stages:
|
|
|
|
```go
|
|
type Phase string
|
|
|
|
const (
|
|
PhaseDraft Phase = "draft" // Initial feature request
|
|
PhaseSpecified Phase = "specified" // Specification document exists
|
|
PhasePlanned Phase = "planned" // Task breakdown complete
|
|
PhaseReady Phase = "ready" // Approved and ready for implementation
|
|
PhaseImplementation Phase = "implementation" // Active coding
|
|
PhaseReview Phase = "review" // Code review in progress
|
|
PhaseAudit Phase = "audit" // Security/quality audit
|
|
PhaseQA Phase = "qa" // Testing and validation
|
|
PhaseMerge Phase = "merge" // Merging to main branch
|
|
PhaseReleased Phase = "released" // Deployed to production
|
|
)
|
|
```
|
|
|
|
**Phase Transition Requirements:**
|
|
|
|
| From Phase | To Phase | Required Artifacts |
|
|
|------------|----------|-------------------|
|
|
| draft | specified | Approved specification |
|
|
| specified | planned | Approved plan with tasks |
|
|
| planned | ready | All task approvals |
|
|
| ready | implementation | None (automatic) |
|
|
| implementation | review | All tasks implemented |
|
|
| review | audit | Review approved |
|
|
| audit | qa | Audit passed |
|
|
| qa | merge | QA passed |
|
|
| merge | released | Merge complete |
|
|
|
|
---
|
|
|
|
### 2. The Deterministic Classifier
|
|
|
|
The classifier comprises **24 priority-ordered rules** that evaluate current state and output specific actions. Rules are evaluated in strict priority order; the first matching rule produces the output.
|
|
|
|
```go
|
|
type ClassifierRule struct {
|
|
Priority int
|
|
Name string
|
|
Condition func(state *FeatureState) bool
|
|
Action Action
|
|
}
|
|
|
|
type Action struct {
|
|
Type ActionType
|
|
Payload map[string]interface{}
|
|
Instruction string
|
|
}
|
|
|
|
type ActionType string
|
|
|
|
const (
|
|
ActionCreateSpec ActionType = "CREATE_SPEC"
|
|
ActionAwaitApproval ActionType = "AWAIT_APPROVAL"
|
|
ActionTransition ActionType = "TRANSITION"
|
|
ActionImplementTask ActionType = "IMPLEMENT_TASK"
|
|
ActionCreateTests ActionType = "CREATE_TESTS"
|
|
ActionRequestReview ActionType = "REQUEST_REVIEW"
|
|
ActionComplete ActionType = "COMPLETE"
|
|
ActionError ActionType = "ERROR"
|
|
)
|
|
```
|
|
|
|
**Example Rules (Priority Order):**
|
|
|
|
```go
|
|
var ClassificationRules = []ClassifierRule{
|
|
// Priority 0: Error states
|
|
{
|
|
Priority: 0,
|
|
Name: "invalid_phase",
|
|
Condition: func(s *FeatureState) bool {
|
|
return !isValidPhase(s.Phase)
|
|
},
|
|
Action: Action{
|
|
Type: ActionError,
|
|
Instruction: "Invalid phase state - manual intervention required",
|
|
},
|
|
},
|
|
|
|
// Priority 1: Draft phase needs specification
|
|
{
|
|
Priority: 1,
|
|
Name: "draft_needs_spec",
|
|
Condition: func(s *FeatureState) bool {
|
|
return s.Phase == PhaseDraft && !s.HasArtifact("spec")
|
|
},
|
|
Action: Action{
|
|
Type: ActionCreateSpec,
|
|
Instruction: "Create specification document based on feature request",
|
|
},
|
|
},
|
|
|
|
// Priority 2: Spec exists but not approved
|
|
{
|
|
Priority: 2,
|
|
Name: "spec_awaiting_approval",
|
|
Condition: func(s *FeatureState) bool {
|
|
return s.Phase == PhaseDraft &&
|
|
s.HasArtifact("spec") &&
|
|
!s.IsArtifactApproved("spec")
|
|
},
|
|
Action: Action{
|
|
Type: ActionAwaitApproval,
|
|
Payload: map[string]interface{}{"artifact": "spec"},
|
|
Instruction: "Specification awaiting approval",
|
|
},
|
|
},
|
|
|
|
// Priority 3: Spec approved, transition to specified
|
|
{
|
|
Priority: 3,
|
|
Name: "transition_to_specified",
|
|
Condition: func(s *FeatureState) bool {
|
|
return s.Phase == PhaseDraft && s.IsArtifactApproved("spec")
|
|
},
|
|
Action: Action{
|
|
Type: ActionTransition,
|
|
Payload: map[string]interface{}{"to_phase": PhaseSpecified},
|
|
Instruction: "Transitioning to specified phase",
|
|
},
|
|
},
|
|
|
|
// Priority 10: Implementation phase - find next task
|
|
{
|
|
Priority: 10,
|
|
Name: "implement_next_task",
|
|
Condition: func(s *FeatureState) bool {
|
|
return s.Phase == PhaseImplementation && s.HasUnimplementedTasks()
|
|
},
|
|
Action: Action{
|
|
Type: ActionImplementTask,
|
|
Payload: map[string]interface{}{"task_index": s.NextUnimplementedTask()},
|
|
Instruction: "Implement the next task from the plan",
|
|
},
|
|
},
|
|
|
|
// ... remaining rules
|
|
}
|
|
```
|
|
|
|
**Classifier Evaluation Algorithm:**
|
|
|
|
```go
|
|
func (c *Classifier) Evaluate(state *FeatureState) Action {
|
|
for _, rule := range c.Rules {
|
|
if rule.Condition(state) {
|
|
return rule.Action
|
|
}
|
|
}
|
|
return Action{
|
|
Type: ActionError,
|
|
Instruction: "No matching rule - undefined state",
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key Innovation:** The classifier is **external to the agent**. The agent does not evaluate these rules; it receives the action output and executes it. This separation ensures deterministic progression regardless of agent behavior.
|
|
|
|
---
|
|
|
|
### 3. Feature State Representation
|
|
|
|
State is persisted in YAML files within the project's git repository:
|
|
|
|
```go
|
|
type FeatureState struct {
|
|
ID string `yaml:"id"`
|
|
Name string `yaml:"name"`
|
|
Phase Phase `yaml:"phase"`
|
|
CreatedAt time.Time `yaml:"created_at"`
|
|
UpdatedAt time.Time `yaml:"updated_at"`
|
|
Artifacts map[string]Artifact `yaml:"artifacts"`
|
|
Tasks []Task `yaml:"tasks,omitempty"`
|
|
Metadata map[string]interface{} `yaml:"metadata,omitempty"`
|
|
}
|
|
|
|
type Artifact struct {
|
|
Type string `yaml:"type"`
|
|
Path string `yaml:"path"`
|
|
Hash string `yaml:"hash"`
|
|
Approved bool `yaml:"approved"`
|
|
ApprovedBy string `yaml:"approved_by,omitempty"`
|
|
ApprovedAt time.Time `yaml:"approved_at,omitempty"`
|
|
}
|
|
|
|
type Task struct {
|
|
Index int `yaml:"index"`
|
|
Title string `yaml:"title"`
|
|
Description string `yaml:"description"`
|
|
Implemented bool `yaml:"implemented"`
|
|
ArtifactPath string `yaml:"artifact_path,omitempty"`
|
|
}
|
|
```
|
|
|
|
**Directory Structure:**
|
|
|
|
```
|
|
.sdlc/
|
|
├── feature.yaml # Main feature state
|
|
├── spec.yaml # Specification artifact
|
|
├── plan.yaml # Task breakdown
|
|
├── tasks/
|
|
│ ├── 0-setup.yaml # Task 0 implementation record
|
|
│ ├── 1-api.yaml # Task 1 implementation record
|
|
│ └── 2-tests.yaml # Task 2 implementation record
|
|
└── reviews/
|
|
└── review-1.yaml # Review feedback
|
|
```
|
|
|
|
**Git-Backed Audit Trail:**
|
|
|
|
Every state change results in a git commit:
|
|
|
|
```go
|
|
func (s *StateManager) UpdateState(state *FeatureState) error {
|
|
// Serialize to YAML
|
|
data, err := yaml.Marshal(state)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal state: %w", err)
|
|
}
|
|
|
|
// Write to file
|
|
path := filepath.Join(".sdlc", "feature.yaml")
|
|
if err := os.WriteFile(path, data, 0644); err != nil {
|
|
return fmt.Errorf("write state: %w", err)
|
|
}
|
|
|
|
// Commit change
|
|
msg := fmt.Sprintf("sdlc: %s -> %s", state.Phase, state.UpdatedAt.Format(time.RFC3339))
|
|
if err := s.git.CommitAll(msg); err != nil {
|
|
return fmt.Errorf("commit state: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Dual-Execution Architecture
|
|
|
|
#### Technical Problem Solved by Dual-Execution
|
|
|
|
The dual-execution architecture solves a fundamental coordination problem in distributed AI agent orchestration. Consider the alternatives:
|
|
|
|
1. **Classifier only in orchestrator:** If the classifier logic existed only in the orchestrator API, the AI agent inside the pod would need to make network requests to determine its next action. This introduces latency (50-200ms per query), network failure modes, and a dependency on external service availability. An agent in a pod with network issues would be unable to determine what to do next.
|
|
|
|
2. **Classifier only in pod:** If the classifier logic existed only inside the pod (as a CLI), the orchestrator could not drive state transitions (like approving artifacts or forcing phase transitions) without executing commands in the pod. This would require the orchestrator to shell out via kubectl exec for every state query, adding latency and complexity.
|
|
|
|
3. **Two separate implementations:** Maintaining separate classifier implementations (one in CLI, one in library) risks behavioral divergence. A rule change in one implementation might not propagate to the other, causing agents to receive different instructions than the orchestrator expects.
|
|
|
|
**The Solution:** By compiling the same classifier code into both a CLI binary (embedded in pod images) and a library (imported by the orchestrator API), the system ensures:
|
|
- **Identical classification behavior** regardless of execution context
|
|
- **Low-latency agent queries** via local CLI invocation
|
|
- **Direct orchestrator transitions** via library import
|
|
- **Single source of truth** for classification rules
|
|
|
|
This architectural decision enables both agent-initiated queries ("what should I do next?") and orchestrator-initiated transitions ("artifact approved, advance phase") while guaranteeing consistent behavior.
|
|
|
|
The SDLC module operates in two execution modes from the same codebase:
|
|
|
|
#### 4.1 CLI Mode (Inside Pod)
|
|
|
|
```go
|
|
// cmd/sdlc/main.go
|
|
func main() {
|
|
app := &cli.App{
|
|
Name: "sdlc",
|
|
Usage: "Software Development Lifecycle CLI",
|
|
Commands: []*cli.Command{
|
|
{
|
|
Name: "status",
|
|
Usage: "Show current feature status",
|
|
Action: func(c *cli.Context) error {
|
|
state, err := sdlc.LoadState(".sdlc")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
classifier := sdlc.NewClassifier()
|
|
action := classifier.Evaluate(state)
|
|
return json.NewEncoder(os.Stdout).Encode(action)
|
|
},
|
|
},
|
|
{
|
|
Name: "record",
|
|
Usage: "Record an artifact",
|
|
Action: func(c *cli.Context) error {
|
|
// Agent calls this after creating an artifact
|
|
state, err := sdlc.LoadState(".sdlc")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
state.AddArtifact(c.String("type"), c.String("path"))
|
|
return sdlc.SaveState(".sdlc", state)
|
|
},
|
|
},
|
|
},
|
|
}
|
|
app.Run(os.Args)
|
|
}
|
|
```
|
|
|
|
**Agent Usage Pattern:**
|
|
|
|
```bash
|
|
# Agent queries what to do next
|
|
$ sdlc status
|
|
{"type":"IMPLEMENT_TASK","payload":{"task_index":2},"instruction":"Implement task 2: Add API endpoint"}
|
|
|
|
# Agent implements the task, then records it
|
|
$ sdlc record --type=task --index=2 --path=internal/handlers/users.go
|
|
|
|
# Agent queries again
|
|
$ sdlc status
|
|
{"type":"IMPLEMENT_TASK","payload":{"task_index":3},"instruction":"Implement task 3: Add tests"}
|
|
```
|
|
|
|
#### 4.2 Library Mode (In Orchestrator)
|
|
|
|
```go
|
|
// internal/service/sdlc_service.go
|
|
type SDLCService struct {
|
|
classifier *sdlc.Classifier
|
|
executor PodExecutor
|
|
}
|
|
|
|
func (s *SDLCService) GetNextAction(projectID, featureID string) (*Action, error) {
|
|
// Load state from pod via kubectl exec
|
|
state, err := s.executor.ReadState(projectID, featureID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read state: %w", err)
|
|
}
|
|
|
|
// Evaluate classifier (same logic as CLI)
|
|
action := s.classifier.Evaluate(state)
|
|
|
|
return action, nil
|
|
}
|
|
|
|
func (s *SDLCService) ApproveArtifact(projectID, featureID, artifactType string) error {
|
|
// Load state
|
|
state, err := s.executor.ReadState(projectID, featureID)
|
|
if err != nil {
|
|
return fmt.Errorf("read state: %w", err)
|
|
}
|
|
|
|
// Update approval
|
|
state.ApproveArtifact(artifactType)
|
|
|
|
// Save state back to pod
|
|
return s.executor.WriteState(projectID, featureID, state)
|
|
}
|
|
```
|
|
|
|
**Key Insight:** Both modes share `sdlc.Classifier` and `sdlc.FeatureState`. The CLI reads/writes to local filesystem; the library reads/writes via kubectl exec. Behavior is identical.
|
|
|
|
---
|
|
|
|
### 5. Isolated Pod Execution
|
|
|
|
Agents execute inside Kubernetes pods with controlled access:
|
|
|
|
```go
|
|
type PodExecutor struct {
|
|
kubeClient kubernetes.Interface
|
|
namespace string
|
|
}
|
|
|
|
func (p *PodExecutor) ExecCommand(podName string, cmd []string) (string, error) {
|
|
req := p.kubeClient.CoreV1().RESTClient().Post().
|
|
Resource("pods").
|
|
Name(podName).
|
|
Namespace(p.namespace).
|
|
SubResource("exec").
|
|
VersionedParams(&corev1.PodExecOptions{
|
|
Command: cmd,
|
|
Stdout: true,
|
|
Stderr: true,
|
|
}, scheme.ParameterCodec)
|
|
|
|
exec, err := remotecommand.NewSPDYExecutor(p.config, "POST", req.URL())
|
|
if err != nil {
|
|
return "", fmt.Errorf("create executor: %w", err)
|
|
}
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
err = exec.Stream(remotecommand.StreamOptions{
|
|
Stdout: &stdout,
|
|
Stderr: &stderr,
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("exec stream: %w (stderr: %s)", err, stderr.String())
|
|
}
|
|
|
|
return stdout.String(), nil
|
|
}
|
|
```
|
|
|
|
**Pod Discovery:**
|
|
|
|
```go
|
|
func (p *PodExecutor) DiscoverProjects() ([]string, error) {
|
|
pods, err := p.kubeClient.CoreV1().Pods(p.namespace).List(context.Background(), metav1.ListOptions{
|
|
LabelSelector: "rdev.orchard9.ai/project=true",
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list pods: %w", err)
|
|
}
|
|
|
|
var projectIDs []string
|
|
for _, pod := range pods.Items {
|
|
if id, ok := pod.Labels["rdev.orchard9.ai/project-id"]; ok {
|
|
projectIDs = append(projectIDs, id)
|
|
}
|
|
}
|
|
return projectIDs, nil
|
|
}
|
|
```
|
|
|
|
**Streaming Output via SSE:**
|
|
|
|
```go
|
|
func (h *ExecutionHandler) StreamExecution(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
|
w.Header().Set("Cache-Control", "no-cache")
|
|
|
|
flusher, ok := w.(http.Flusher)
|
|
if !ok {
|
|
http.Error(w, "streaming not supported", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Execute command with streaming
|
|
err := h.executor.ExecCommandStreaming(podName, cmd, func(line string) {
|
|
fmt.Fprintf(w, "data: %s\n\n", line)
|
|
flusher.Flush()
|
|
})
|
|
|
|
if err != nil {
|
|
fmt.Fprintf(w, "event: error\ndata: %s\n\n", err.Error())
|
|
}
|
|
fmt.Fprintf(w, "event: done\ndata: completed\n\n")
|
|
flusher.Flush()
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 6. Composable Monorepo Templates
|
|
|
|
Templates are embedded at compile time and composed at runtime:
|
|
|
|
#### 6.1 Embedded Templates
|
|
|
|
```go
|
|
//go:embed templates/*
|
|
var templatesFS embed.FS
|
|
|
|
type TemplateProvider struct {
|
|
fs embed.FS
|
|
}
|
|
|
|
func (p *TemplateProvider) LoadSkeleton() (*Template, error) {
|
|
return p.loadTemplate("templates/skeleton")
|
|
}
|
|
|
|
func (p *TemplateProvider) LoadComponent(componentType string) (*Template, error) {
|
|
return p.loadTemplate(fmt.Sprintf("templates/components/%s", componentType))
|
|
}
|
|
```
|
|
|
|
#### 6.2 Template Composition
|
|
|
|
```go
|
|
type CompositionEngine struct {
|
|
provider *TemplateProvider
|
|
}
|
|
|
|
func (c *CompositionEngine) Compose(config CompositionConfig) (*ComposedProject, error) {
|
|
// Load skeleton
|
|
skeleton, err := c.provider.LoadSkeleton()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load skeleton: %w", err)
|
|
}
|
|
|
|
// Process skeleton with project variables
|
|
files := skeleton.Process(map[string]string{
|
|
"ProjectName": config.ProjectName,
|
|
"GoModule": config.GoModule,
|
|
})
|
|
|
|
// Add each component
|
|
for _, comp := range config.Components {
|
|
component, err := c.provider.LoadComponent(comp.Type)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load component %s: %w", comp.Type, err)
|
|
}
|
|
|
|
// Process component with component-specific variables
|
|
componentFiles := component.Process(map[string]string{
|
|
"ComponentName": comp.Name,
|
|
"ComponentNameCamel": toCamelCase(comp.Name),
|
|
"Port": strconv.Itoa(comp.Port),
|
|
"ProjectName": config.ProjectName,
|
|
})
|
|
|
|
files = append(files, componentFiles...)
|
|
}
|
|
|
|
return &ComposedProject{Files: files}, nil
|
|
}
|
|
```
|
|
|
|
#### 6.3 Atomic Deployment
|
|
|
|
```go
|
|
func (d *Deployer) DeployAtomic(projectID string, files []File) error {
|
|
// Collect all file operations
|
|
operations := make([]gitea.FileOperation, len(files))
|
|
for i, f := range files {
|
|
operations[i] = gitea.FileOperation{
|
|
Operation: "create",
|
|
Path: f.Path,
|
|
Content: base64.StdEncoding.EncodeToString(f.Content),
|
|
}
|
|
}
|
|
|
|
// Single API call creates all files in one commit
|
|
_, _, err := d.giteaClient.ChangeFiles(
|
|
d.owner,
|
|
projectID,
|
|
gitea.ChangeFilesOptions{
|
|
Files: operations,
|
|
Message: "Initialize project from template",
|
|
Branch: "main",
|
|
},
|
|
)
|
|
|
|
return err
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 7. Per-Project Worker Coordination
|
|
|
|
Workers poll for tasks specific to their assigned project:
|
|
|
|
```go
|
|
type QueueProcessor struct {
|
|
db *sqlx.DB
|
|
coordinator *Coordinator
|
|
}
|
|
|
|
func (q *QueueProcessor) Start(ctx context.Context) {
|
|
// Coordinator spawns per-project workers
|
|
go q.coordinator.Run(ctx)
|
|
}
|
|
|
|
type Coordinator struct {
|
|
kubeClient kubernetes.Interface
|
|
workers map[string]*ProjectWorker
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func (c *Coordinator) Run(ctx context.Context) {
|
|
ticker := time.NewTicker(30 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
c.syncWorkers(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Coordinator) syncWorkers(ctx context.Context) {
|
|
// Discover projects
|
|
projects, err := c.discoverProjects()
|
|
if err != nil {
|
|
slog.Error("discover projects", "error", err)
|
|
return
|
|
}
|
|
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
// Start workers for new projects
|
|
for _, projectID := range projects {
|
|
if _, exists := c.workers[projectID]; !exists {
|
|
worker := NewProjectWorker(projectID, c.db, c.executors)
|
|
c.workers[projectID] = worker
|
|
go worker.Run(ctx)
|
|
}
|
|
}
|
|
|
|
// Stop workers for removed projects
|
|
for projectID, worker := range c.workers {
|
|
if !contains(projects, projectID) {
|
|
worker.Stop()
|
|
delete(c.workers, projectID)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Atomic Task Acquisition:**
|
|
|
|
```go
|
|
func (w *ProjectWorker) acquireTask(ctx context.Context) (*WorkTask, error) {
|
|
tx, err := w.db.BeginTxx(ctx, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("begin tx: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var task WorkTask
|
|
err = tx.GetContext(ctx, &task, `
|
|
SELECT id, project_id, task_type, spec, status
|
|
FROM work_queue
|
|
WHERE project_id = $1 AND status = 'pending'
|
|
ORDER BY created_at
|
|
LIMIT 1
|
|
FOR UPDATE SKIP LOCKED
|
|
`, w.projectID)
|
|
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil // No tasks available
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("select task: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `
|
|
UPDATE work_queue SET status = 'processing', started_at = NOW()
|
|
WHERE id = $1
|
|
`, task.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("update status: %w", err)
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return nil, fmt.Errorf("commit: %w", err)
|
|
}
|
|
|
|
return &task, nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 8. Performance Characteristics
|
|
|
|
#### 8.1 Classification Latency
|
|
|
|
| Rules | p50 Latency | p99 Latency |
|
|
|-------|-------------|-------------|
|
|
| 10 | 0.1ms | 0.5ms |
|
|
| 24 | 0.2ms | 1.0ms |
|
|
| 50 | 0.5ms | 2.0ms |
|
|
|
|
#### 8.2 Pod Execution Latency
|
|
|
|
| Operation | p50 Latency | p99 Latency |
|
|
|-----------|-------------|-------------|
|
|
| kubectl exec (simple) | 50ms | 200ms |
|
|
| State read | 100ms | 500ms |
|
|
| State write + commit | 500ms | 2s |
|
|
|
|
#### 8.3 Work Queue Throughput
|
|
|
|
| Concurrent Workers | Tasks/sec | Notes |
|
|
|-------------------|-----------|-------|
|
|
| 1 | 10 | Single project |
|
|
| 10 | 80 | 10 projects, row-lock contention minimal |
|
|
| 100 | 500 | Scales with PostgreSQL connections |
|
|
|
|
---
|
|
|
|
## Alternative Embodiments
|
|
|
|
### 9A. Alternative Execution Environments
|
|
|
|
The system may use alternative execution environments:
|
|
- Docker containers instead of Kubernetes pods
|
|
- SSH connections instead of kubectl exec
|
|
- WebSocket instead of exec streaming
|
|
|
|
### 9B. Alternative State Storage
|
|
|
|
State may be stored in:
|
|
- Database tables instead of git-backed YAML
|
|
- S3-compatible object storage
|
|
- CRDT-based distributed state for multi-region
|
|
|
|
### 9C. Alternative Classifier Implementations
|
|
|
|
The classifier may be implemented as:
|
|
- Rule engine (Drools, OPA/Rego)
|
|
- Decision tree loaded from configuration
|
|
- ML model with deterministic output mapping
|
|
|
|
---
|
|
|
|
## Claims
|
|
|
|
[See patent-disclosure.md for full claim listing]
|
|
|
|
---
|
|
|
|
## Abstract
|
|
|
|
A system and method for orchestrating AI agents through software development workflows using deterministic classification. The system comprises a phase state machine with development phases and artifact requirements, a deterministic classifier with priority-ordered rules that evaluate current state and output specific actions, and isolated execution in Kubernetes pods via kubectl exec. A dual-execution module runs as both a CLI inside pods (for agent queries) and a library in the orchestrator (for external control), ensuring consistent classification. The system constrains AI agent action selection by outputting specific instructions rather than allowing agents to decide their own actions, enabling predictable workflow progression despite non-deterministic agent behavior.
|
|
|
|
---
|
|
|
|
## Revision History
|
|
|
|
| Date | Author | Changes |
|
|
|------|--------|---------|
|
|
| 2026-02-04 | Initial | Complete specification with data structures, algorithms, and code examples |
|