+ The autonomous pipeline (v0.10.0) defines explicit gates where human
+ approval is required. Always-human gates are hardcoded and cannot be
+ overridden. Configurable gates show their default; the actual runtime
+ value is set per-project in{' '}
+
+ .agentd/agents/conductor.yml
+
+ .
+
+
+ {/* Always-human gates */}
+
+
+
+ Always requires human
+
+
+ {alwaysGates.map((gate) => (
+
+ ))}
+
+
+
+ {/* Configurable gates */}
+
+
+
+ Configurable gates
+
+
+ {configurableGates.map((gate) => (
+
+ ))}
+
+
+ To change a configurable gate, update the{' '}
+ gates: section in conductor.yml and
+ redeploy the agent.
+
+
+
+ )
+}
+
+export default PipelineGates
diff --git a/ui/src/hooks/usePipelineStatus.ts b/ui/src/hooks/usePipelineStatus.ts
new file mode 100644
index 00000000..505659b4
--- /dev/null
+++ b/ui/src/hooks/usePipelineStatus.ts
@@ -0,0 +1,49 @@
+/**
+ * usePipelineStatus — fetches the current autonomous pipeline state.
+ *
+ * Status: stub / not yet wired.
+ *
+ * The Conductor agent (v0.10.0, issue #603) will expose pipeline state by
+ * writing structured memories tagged conductor+pipeline after each run.
+ * This hook will be updated to query the memory service (or a dedicated
+ * orchestrator endpoint) once that work lands.
+ *
+ * Until then it returns { status: null, loading: false } so the
+ * PipelineStatusCard renders its "not yet active" empty state instead
+ * of a perpetual spinner.
+ */
+
+import { useEffect, useState } from 'react'
+import type { PipelineStatus } from '@/types/pipeline'
+
+export interface UsePipelineStatusResult {
+ status: PipelineStatus | null
+ loading: boolean
+ error?: string
+ /** Manually re-fetch */
+ refetch: () => void
+}
+
+export function usePipelineStatus(): UsePipelineStatusResult {
+ const [status] = useState(null)
+ const [loading] = useState(false)
+ const [error] = useState(undefined)
+ const [, setTick] = useState(0)
+
+ // Placeholder effect — replace with real fetch when conductor endpoint exists.
+ useEffect(() => {
+ // TODO(v0.10.0): query memory service for latest conductor pipeline digest.
+ // Example query:
+ // memoryClient.search('pipeline state merge queue', {
+ // tags: ['conductor', 'pipeline'],
+ // limit: 1,
+ // })
+ }, [])
+
+ return {
+ status,
+ loading,
+ error,
+ refetch: () => setTick((t) => t + 1),
+ }
+}
diff --git a/ui/src/pages/DashboardPage.tsx b/ui/src/pages/DashboardPage.tsx
index 5357f9cb..08aa569a 100644
--- a/ui/src/pages/DashboardPage.tsx
+++ b/ui/src/pages/DashboardPage.tsx
@@ -12,10 +12,12 @@ import {
import { AgentSummary } from '@/components/dashboard/AgentSummary'
import { NotificationSummary } from '@/components/dashboard/NotificationSummary'
import { ActivityTimeline } from '@/components/dashboard/ActivityTimeline'
+import { PipelineStatusCard } from '@/components/dashboard/PipelineStatusCard'
import type { ActivityEvent } from '@/components/dashboard/ActivityTimeline'
import { useServiceHealth } from '@/hooks/useServiceHealth'
import { useAgentSummary } from '@/hooks/useAgentSummary'
import { useNotificationSummary } from '@/hooks/useNotificationSummary'
+import { usePipelineStatus } from '@/hooks/usePipelineStatus'
// ---------------------------------------------------------------------------
// Stub "Coming Soon" card
@@ -48,6 +50,7 @@ export function DashboardPage() {
const { services, loading: healthLoading, initializing: healthInit, refresh } = useServiceHealth()
const agentSummary = useAgentSummary()
const notifSummary = useNotificationSummary()
+ const { status: pipelineStatus, loading: pipelineLoading, error: pipelineError, refetch: pipelineRefetch } = usePipelineStatus()
// Build a synthetic activity feed from available data
const activityEvents: ActivityEvent[] = useMemo(() => {
@@ -117,12 +120,22 @@ export function DashboardPage() {
error={agentSummary.error}
/>
- {/* Stub sections */}
+ {/* Pipeline status + monitoring stub */}
+ }
/>
+
+
+ {/* Remaining stubs */}
+
} />
diff --git a/ui/src/pages/SettingsPage.tsx b/ui/src/pages/SettingsPage.tsx
index 92075ea1..5b7f34fb 100644
--- a/ui/src/pages/SettingsPage.tsx
+++ b/ui/src/pages/SettingsPage.tsx
@@ -7,6 +7,7 @@ import { useRef, useState } from 'react'
import { ServiceConfig } from '@/components/settings/ServiceConfig'
import { UIPreferences } from '@/components/settings/UIPreferences'
import { AboutSection } from '@/components/settings/AboutSection'
+import { PipelineGates } from '@/components/settings/PipelineGates'
import { useSettings } from '@/hooks/useSettings'
import { resetSettings } from '@/stores/settingsStore'
import type { Settings } from '@/stores/settingsStore'
@@ -83,6 +84,17 @@ export function SettingsPage() {
+ {/* Pipeline Gates */}
+
+
+ Autonomous Pipeline Gates
+
+
+ v0.10.0 — defines where human approval is required in the autonomous pipeline.
+
+
+
+
{/* About */}
About
diff --git a/ui/src/types/pipeline.ts b/ui/src/types/pipeline.ts
new file mode 100644
index 00000000..eb66aa8b
--- /dev/null
+++ b/ui/src/types/pipeline.ts
@@ -0,0 +1,164 @@
+/**
+ * Pipeline types for the v0.9.0 Autonomous Development Pipeline.
+ *
+ * The Conductor agent (`.agentd/agents/conductor.yml`, issue #620) manages
+ * the merge queue and git-spice stack state. The label-driven state machine
+ * is defined in issue #640; human approval gates are defined in issue #643.
+ *
+ * These types represent the data shape exposed to the UI. Until the conductor
+ * endpoint exists, usePipelineStatus() returns null and PipelineStatusCard
+ * renders its "not yet active" empty state.
+ */
+
+// ---------------------------------------------------------------------------
+// Pipeline stage state machine
+// ---------------------------------------------------------------------------
+
+/**
+ * The 7-stage label-driven pipeline state machine (issue #640).
+ *
+ * Each issue advances through these stages as agents act on it:
+ * issue-created → triage → enrich → implement → review → merge → document
+ *
+ * The Conductor monitors stage transitions via GitHub label changes.
+ */
+export type PipelineStage =
+ | 'issue-created'
+ | 'triage'
+ | 'enrich'
+ | 'implement'
+ | 'review'
+ | 'merge'
+ | 'document'
+
+export const PIPELINE_STAGE_LABELS: Record = {
+ 'issue-created': 'Issue Created',
+ triage: 'Triage',
+ enrich: 'Enrich',
+ implement: 'Implement',
+ review: 'Review',
+ merge: 'Merge',
+ document: 'Document',
+}
+
+export const PIPELINE_STAGE_ORDER: PipelineStage[] = [
+ 'issue-created',
+ 'triage',
+ 'enrich',
+ 'implement',
+ 'review',
+ 'merge',
+ 'document',
+]
+
+// ---------------------------------------------------------------------------
+// Merge queue
+// ---------------------------------------------------------------------------
+
+/** A single PR waiting in the merge queue */
+export interface PipelineQueueItem {
+ /** GitHub PR number */
+ prNumber: number
+ /** PR title (truncated for display) */
+ title: string
+ /** Head branch name */
+ branch: string
+ /** Base branch (trunk = "main", or parent stack branch) */
+ baseBranch: string
+ /**
+ * Stack depth: 0 = branches directly from trunk (main),
+ * N = Nth level in a git-spice stack.
+ */
+ stackDepth: number
+ /** Approved by the reviewer agent */
+ approved: boolean
+ /** CI status. null = checks still running */
+ ciPassing: boolean | null
+}
+
+/** Aggregate pipeline state posted by the Conductor every 4 hours */
+export interface PipelineStatus {
+ /** PRs in the merge queue, ordered bottom-of-stack first */
+ mergeQueue: PipelineQueueItem[]
+ /** Number of distinct open stacks (groups of stacked branches) */
+ activeStackCount: number
+ /** PRs with no activity (commits or review comments) for > 7 days */
+ staleCount: number
+ /** ISO timestamp of the last `git-spice repo sync` run */
+ lastSyncAt: string | null
+ /** ISO timestamp of the last full conductor run */
+ conductorLastRunAt: string | null
+}
+
+// ---------------------------------------------------------------------------
+// Human interaction gates
+// ---------------------------------------------------------------------------
+
+/**
+ * Human interaction gate definitions (issue #643).
+ *
+ * Always-human gates are hardcoded in the conductor system prompt and cannot
+ * be changed via the UI. Configurable gates default to autonomous but can
+ * be flipped per-project in the agent YAML.
+ */
+export interface PipelineGate {
+ id: string
+ label: string
+ description: string
+ /** 'always' = hardcoded human required; 'configurable' = default autonomous */
+ kind: 'always' | 'configurable'
+ /** Default state for configurable gates */
+ defaultAutonomous?: boolean
+}
+
+export const PIPELINE_GATES: PipelineGate[] = [
+ // Always-human gates
+ {
+ id: 'git-spice-auth',
+ label: 'git-spice authentication',
+ description: 'One-time GitHub auth for git-spice must be performed by a human operator.',
+ kind: 'always',
+ },
+ {
+ id: 'production-deploy',
+ label: 'Production deployments',
+ description: 'Any deployment to a production environment requires human sign-off.',
+ kind: 'always',
+ },
+ {
+ id: 'security-changes',
+ label: 'Security-sensitive changes',
+ description:
+ 'Changes to authentication, secrets handling, or cryptography require human review.',
+ kind: 'always',
+ },
+ {
+ id: 'conflict-escalation',
+ label: 'Merge conflict escalation',
+ description:
+ 'When the Conductor cannot automatically restack a conflict, it escalates to a human.',
+ kind: 'always',
+ },
+ // Configurable gates
+ {
+ id: 'pr-auto-merge',
+ label: 'PR auto-merge',
+ description: 'Conductor merges approved, CI-passing PRs without human confirmation.',
+ kind: 'configurable',
+ defaultAutonomous: true,
+ },
+ {
+ id: 'issue-auto-close',
+ label: 'Issue auto-close',
+ description: 'Issues are automatically closed when their implementation PR merges.',
+ kind: 'configurable',
+ defaultAutonomous: true,
+ },
+ {
+ id: 'new-dependency',
+ label: 'New dependency additions',
+ description: 'Adding a new crate or package dependency requires human approval.',
+ kind: 'configurable',
+ defaultAutonomous: false,
+ },
+]