Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,11 @@ class AmpereCommand(
jazzPane.setPhase(CognitiveProgressPane.Phase.INITIALIZING, "Finding issue #$issue...")
agentScope.launch {
try {
val availableIssues = context.codeAgent.queryAvailableIssues()
val availableIssues = context.codeIssueWorkflow.queryAvailableIssues()
val targetIssue = availableIssues.find { it.number == issue }
if (targetIssue != null) {
jazzPane.setPhase(CognitiveProgressPane.Phase.PERCEIVE, "Working on issue #$issue...")
val issueResult = context.codeAgent.workOnIssue(targetIssue)
val issueResult = context.codeIssueWorkflow.workOnIssue(targetIssue, context.codeAgent)
if (issueResult.isSuccess) {
jazzPane.setPhase(CognitiveProgressPane.Phase.COMPLETED)
} else {
Expand Down
60 changes: 43 additions & 17 deletions ampere-cli/src/jvmMain/kotlin/link/socket/ampere/AmpereContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.serialization.json.Json
import link.socket.ampere.agents.definition.AgentId
import link.socket.ampere.agents.definition.CodeAgent
import link.socket.ampere.agents.definition.SparkBasedAgent
import link.socket.ampere.agents.definition.code.CodeState
import link.socket.ampere.agents.domain.knowledge.KnowledgeRepository
import link.socket.ampere.agents.domain.knowledge.KnowledgeRepositoryImpl
import link.socket.ampere.agents.domain.outcome.OutcomeMemoryRepository
import link.socket.ampere.agents.domain.event.Event
import link.socket.ampere.agents.domain.memory.AgentMemoryService
import link.socket.ampere.agents.execution.AutonomousWorkLoop
import link.socket.ampere.agents.execution.WorkLoopConfig
import link.socket.ampere.agents.execution.issue.CodeIssueWorkflow
import link.socket.ampere.integrations.issues.IssueTrackerProvider
import link.socket.ampere.agents.environment.EnvironmentService
import link.socket.ampere.agents.environment.workspace.ExecutionWorkspace
import link.socket.ampere.agents.environment.workspace.defaultWorkspace
Expand Down Expand Up @@ -225,29 +228,41 @@ class AmpereContext(
}

/**
* CodeAgent instance for autonomous work.
* Set when createAutonomousWorkLoop() is called.
* The code agent instance for autonomous work. Set when
* [createAutonomousWorkLoop] is called.
*/
private var _codeAgent: CodeAgent? = null
private var _codeAgent: SparkBasedAgent<CodeState>? = null

/**
* Access the CodeAgent instance.
* Throws an error if not initialized via createAutonomousWorkLoop().
* Access the code agent instance.
* Throws an error if not initialized via [createAutonomousWorkLoop].
*/
val codeAgent: CodeAgent
get() = _codeAgent ?: error("CodeAgent not initialized. Call createAutonomousWorkLoop() first.")
val codeAgent: SparkBasedAgent<CodeState>
get() = _codeAgent ?: error("codeAgent not initialized. Call createAutonomousWorkLoop() first.")

/**
* Autonomous work loop for CodeAgent.
* Manages continuous polling and processing of GitHub issues.
* The issue → task → PR workflow paired with the code agent. Owns the
* claim/work-on/update lifecycle that used to live on the legacy
* `CodeAgent`. Set when [createAutonomousWorkLoop] is called.
*/
private var _autonomousWorkLoop: AutonomousWorkLoop? = null
private var _codeIssueWorkflow: CodeIssueWorkflow? = null

val codeIssueWorkflow: CodeIssueWorkflow
get() = _codeIssueWorkflow ?: error(
"codeIssueWorkflow not initialized. Call createAutonomousWorkLoop() first.",
)

/**
* Autonomous work loop for the code agent. Manages continuous polling
* and processing of GitHub issues.
*/
private var _autonomousWorkLoop: AutonomousWorkLoop<CodeState>? = null

/**
* Access the autonomous work loop.
* Throws an error if not initialized via createAutonomousWorkLoop().
* Throws an error if not initialized via [createAutonomousWorkLoop].
*/
val autonomousWorkLoop: AutonomousWorkLoop
val autonomousWorkLoop: AutonomousWorkLoop<CodeState>
get() = _autonomousWorkLoop ?: error("Autonomous work loop not initialized. Call createAutonomousWorkLoop() first.")

/**
Expand Down Expand Up @@ -316,23 +331,34 @@ class AmpereContext(
}

/**
* Create and initialize the autonomous work loop for a CodeAgent.
* Create and initialize the autonomous work loop for a code agent.
*
* This must be called before attempting to start autonomous work.
* The work loop is connected to the shared event bus so that work
* events are visible in the dashboard.
*
* @param codeAgent The CodeAgent instance that will process issues
* @param codeAgent The agent instance that will process issues
* @param issueTrackerProvider Source of issues (typically GitHub)
* @param repository Repository identifier the provider scopes its queries to
* @param config Optional configuration for work loop behavior
* @return The created AutonomousWorkLoop instance
*/
fun createAutonomousWorkLoop(
codeAgent: CodeAgent,
codeAgent: SparkBasedAgent<CodeState>,
issueTrackerProvider: IssueTrackerProvider,
repository: String,
config: WorkLoopConfig = WorkLoopConfig(),
): AutonomousWorkLoop {
): AutonomousWorkLoop<CodeState> {
val workflow = CodeIssueWorkflow(
issueTrackerProvider = issueTrackerProvider,
repository = repository,
agentId = codeAgent.id,
)
_codeAgent = codeAgent
_codeIssueWorkflow = workflow
_autonomousWorkLoop = AutonomousWorkLoop(
agent = codeAgent,
workflow = workflow,
config = config,
scope = scope,
eventApiFactory = { agentId -> environmentService.createEventApi(agentId) },
Expand Down
30 changes: 19 additions & 11 deletions ampere-cli/src/jvmMain/kotlin/link/socket/ampere/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import java.io.File
import kotlinx.coroutines.runBlocking
import link.socket.ampere.agents.definition.AgentFactory
import link.socket.ampere.agents.definition.AgentType
import link.socket.ampere.agents.definition.CodeAgent
import link.socket.ampere.agents.definition.ProductAgent
import link.socket.ampere.agents.definition.QualityAgent
import link.socket.ampere.agents.definition.SparkBasedAgent
import link.socket.ampere.agents.definition.code.CodeState
import link.socket.ampere.agents.definition.product.ProductState
import link.socket.ampere.agents.definition.qa.QualityState
import link.socket.ampere.config.AmpereConfig
import link.socket.ampere.config.ConfigConverter
import link.socket.ampere.config.ConfigParser
Expand Down Expand Up @@ -95,20 +96,27 @@ fun main(args: Array<String>) {
// Create agents based on team configuration (or defaults if no config)
val teamRoles = config?.team?.map { it.role } ?: listOf("engineer", "product-manager", "qa-tester")

val codeAgent: CodeAgent? = if (teamRoles.any { it == "engineer" || it == "code" }) {
agentFactory.create<CodeAgent>(AgentType.CODE).also { it.initialize(context.scope) }
val codeAgent: SparkBasedAgent<CodeState>? = if (teamRoles.any { it == "engineer" || it == "code" }) {
agentFactory.create<SparkBasedAgent<CodeState>>(AgentType.CODE).also { it.initialize(context.scope) }
} else null

val productAgent: ProductAgent? = if (teamRoles.any { it == "product-manager" || it == "product" }) {
agentFactory.create<ProductAgent>(AgentType.PRODUCT).also { it.initialize(context.scope) }
val productAgent: SparkBasedAgent<ProductState>? = if (teamRoles.any { it == "product-manager" || it == "product" }) {
agentFactory.create<SparkBasedAgent<ProductState>>(AgentType.PRODUCT).also { it.initialize(context.scope) }
} else null

val qualityAgent: QualityAgent? = if (teamRoles.any { it == "qa-tester" || it == "quality" }) {
agentFactory.create<QualityAgent>(AgentType.QUALITY).also { it.initialize(context.scope) }
val qualityAgent: SparkBasedAgent<QualityState>? = if (teamRoles.any { it == "qa-tester" || it == "quality" }) {
agentFactory.create<SparkBasedAgent<QualityState>>(AgentType.QUALITY).also { it.initialize(context.scope) }
} else null

// Initialize autonomous work loop for CodeAgent (if present in team)
codeAgent?.let { context.createAutonomousWorkLoop(it) }
// Initialize autonomous work loop for the code agent (if present in team
// and a repository was detected for the issue tracker).
if (codeAgent != null && repository != null) {
context.createAutonomousWorkLoop(
codeAgent = codeAgent,
issueTrackerProvider = issueTrackerProvider,
repository = repository,
)
}

try {
// Start all orchestrator services
Expand Down
10 changes: 6 additions & 4 deletions ampere-cli/src/jvmMain/kotlin/link/socket/ampere/WorkCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class WorkCommand(
terminal.println()

// Show what would happen
val issues = context.codeAgent.queryAvailableIssues()
val issues = context.codeIssueWorkflow.queryAvailableIssues()
terminal.println("Would work on ${issues.size} available issue(s)")
issues.take(5).forEach { issue ->
terminal.println(" #${issue.number}: ${issue.title}")
Expand All @@ -134,7 +134,9 @@ class WorkCommand(
}
} else {
// Work on single issue
val issues = context.codeAgent.queryAvailableIssues()
val workflow = context.codeIssueWorkflow
val agent = context.codeAgent
val issues = workflow.queryAvailableIssues()

if (issues.isEmpty()) {
terminal.println(yellow("No available issues found"))
Expand All @@ -147,13 +149,13 @@ class WorkCommand(

terminal.println("Working on issue #${issue.number}: ${issue.title}")

val claimed = context.codeAgent.claimIssue(issue.number)
val claimed = workflow.claimIssue(issue.number)
if (claimed.isFailure) {
terminal.println(red("Failed to claim issue: ${claimed.exceptionOrNull()?.message}"))
return@runBlocking
}

val result = context.codeAgent.workOnIssue(issue)
val result = workflow.workOnIssue(issue, agent)
if (result.isSuccess) {
terminal.println(green("✓ Successfully completed issue #${issue.number}"))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import kotlinx.coroutines.withContext
import kotlinx.datetime.Clock
import link.socket.ampere.AmpereContext
import link.socket.ampere.agents.config.AgentActionAutonomy
import link.socket.ampere.agents.definition.CodeAgent
import link.socket.ampere.agents.definition.AgentFactory
import link.socket.ampere.agents.definition.AgentType
import link.socket.ampere.agents.definition.SparkBasedAgent
import link.socket.ampere.agents.definition.code.CodeState
import link.socket.ampere.agents.domain.event.Event
import link.socket.ampere.agents.domain.event.TicketEvent
import link.socket.ampere.agents.domain.outcome.ExecutionOutcome
Expand Down Expand Up @@ -50,7 +51,7 @@ class GoalHandler(
private val aiConfiguration: AIConfiguration? = null,
) {
private var currentActivation: GoalActivation? = null
private var currentAgent: CodeAgent? = null
private var currentAgent: SparkBasedAgent<CodeState>? = null
private var eventApi: AgentEventApi? = null

/**
Expand Down Expand Up @@ -104,8 +105,8 @@ class GoalHandler(
toolWriteCodeFileOverride = writeCodeTool,
)

// Create CodeAgent
val agent = agentFactory.create<CodeAgent>(AgentType.CODE)
// Create spark-based code agent
val agent = agentFactory.create<SparkBasedAgent<CodeState>>(AgentType.CODE)
currentAgent = agent

// Create event API for this agent to publish events
Expand Down Expand Up @@ -170,7 +171,7 @@ class GoalHandler(
* Handle ticket assignment by running the PROPEL cognitive cycle.
*/
private suspend fun handleTicketAssignment(
agent: CodeAgent,
agent: SparkBasedAgent<CodeState>,
ticketId: String,
) {
val api = eventApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import kotlinx.datetime.Clock
import link.socket.ampere.AmpereContext
import link.socket.ampere.agents.definition.AgentFactory
import link.socket.ampere.agents.definition.AgentType
import link.socket.ampere.agents.definition.CodeAgent
import link.socket.ampere.agents.definition.SparkBasedAgent
import link.socket.ampere.agents.definition.code.CodeState
import link.socket.ampere.agents.config.AgentActionAutonomy
import link.socket.ampere.agents.domain.cognition.sparks.CognitivePhase
import link.socket.ampere.agents.domain.cognition.sparks.PhaseSparkManager
Expand Down Expand Up @@ -108,7 +109,7 @@ fun main(escalation: Boolean = false) {
)

// Create CodeWriterAgent
val agent = agentFactory.create<CodeAgent>(AgentType.CODE)
val agent = agentFactory.create<SparkBasedAgent<CodeState>>(AgentType.CODE)

println("🤖 CodeWriterAgent created")
println(" Agent ID: ${agent.id}")
Expand Down Expand Up @@ -376,7 +377,7 @@ internal fun findGeneratedFiles(outputDir: File): GeneratedFiles? {
* Handle ticket assignment by running the cognitive cycle.
*/
private suspend fun handleTicketAssignment(
agent: CodeAgent,
agent: SparkBasedAgent<CodeState>,
ticketId: String,
context: AmpereContext,
eventApi: AgentEventApi,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import link.socket.ampere.AmpereContext
import link.socket.ampere.agents.config.AgentActionAutonomy
import link.socket.ampere.agents.definition.AgentFactory
import link.socket.ampere.agents.definition.AgentType
import link.socket.ampere.agents.definition.CodeAgent
import link.socket.ampere.agents.definition.ProjectAgent
import link.socket.ampere.agents.definition.SparkBasedAgent
import link.socket.ampere.agents.definition.code.CodeState
import link.socket.ampere.agents.definition.project.ProjectState
import link.socket.ampere.agents.domain.Urgency
import link.socket.ampere.agents.domain.event.EventId
import link.socket.ampere.agents.domain.event.EventSource
Expand Down Expand Up @@ -81,7 +82,7 @@ class MultiAgentDemoRunner(
model = AIModel_Claude.Sonnet_4
),
)
val coordinator = coordinatorFactory.create<ProjectAgent>(AgentType.PROJECT)
val coordinator = coordinatorFactory.create<SparkBasedAgent<ProjectState>>(AgentType.PROJECT)
val coordinatorEventApi = context.environmentService.createEventApi(coordinator.id)

// Create worker agent (CodeWriter role)
Expand All @@ -97,7 +98,7 @@ class MultiAgentDemoRunner(
),
toolWriteCodeFileOverride = writeCodeTool,
)
val worker = workerFactory.create<CodeAgent>(AgentType.CODE)
val worker = workerFactory.create<SparkBasedAgent<CodeState>>(AgentType.CODE)
val workerEventApi = context.environmentService.createEventApi(worker.id)

// Set coordinator and worker info on progress pane
Expand Down

This file was deleted.

Loading
Loading