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
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ A new `ILifecycleTask` requires ALL of:
- [ ] `ExtensionDescriptor` (UI field definitions via `getExtensionDescriptor()`)
- [ ] Unit test with Mockito

All task implementations MUST implement: `getId()`, `getType()`, `execute()`, `configure()`, `getExtensionDescriptor()`.
All task implementations MUST implement: `getId()` (returns `TaskId`), `getType()`, `execute()`, `configure()`, `getExtensionDescriptor()`.

### 4.4 Code Patterns

Expand Down Expand Up @@ -441,7 +441,7 @@ public class MyFeatureTask implements ILifecycleTask {
this.dataFactory = dataFactory;
}

@Override public String getId() { return ID; }
@Override public TaskId getId() { return new TaskId(ID); }
@Override public String getType() { return KEY_MYFEATURE; }
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@Override
Expand Down Expand Up @@ -487,7 +487,7 @@ public class MyFeatureTask implements ILifecycleTask {

@Override
public ExtensionDescriptor getExtensionDescriptor() {
ExtensionDescriptor descriptor = new ExtensionDescriptor(ID);
ExtensionDescriptor descriptor = new ExtensionDescriptor(getId());
descriptor.setDisplayName("My Feature");
ConfigValue uriConfig = new ConfigValue("Resource URI", FieldType.URI, false, null);
descriptor.getConfigs().put("uri", uriConfig);
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ A typical agent lifecycle includes these task types:

```java
public interface ILifecycleTask {
String getId();
TaskId getId();
String getType();
void execute(IConversationMemory memory, Object component)
throws LifecycleException;
Expand Down
4 changes: 2 additions & 2 deletions docs/developer-quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,8 @@ Create a custom lifecycle task:
@ApplicationScoped
public class MyCustomTask implements ILifecycleTask {
@Override
public String getId() {
return "ai.labs.mycompany.customtask";
public TaskId getId() {
return new TaskId("ai.labs.mycompany.customtask");
}

@Override
Expand Down
54 changes: 40 additions & 14 deletions mise.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,49 @@
[tools]
java = "temurin-25.0.3+9.0.LTS"
maven = "3.9.12"
"npm:chromadb" = "3.4.3"

[tasks]
dev = { run = "mvn compile quarkus:dev", description = "Start dev mode with live reload (port 7070)" }
compile = { run = "mvn compile", description = "Compile sources only (fast feedback)" }
[tasks.dev]
depends = ["clean"]
run = "mvn compile quarkus:dev"
description = "Start dev mode with live reload (port 7070)"

clean-build = { run = "mvn clean compile", description = "Clean build — delete target/ and recompile from scratch" }
test = { run = "mvn test", description = "Run unit tests (excludes *IT.java integration tests)" }
[tasks.compile]
depends = ["clean"]
run = "mvn compile"
description = "Compile sources only (fast feedback)"

verify = { run = "mvn verify -DskipITs", description = "Compile + unit tests + package (no integration tests)" }
full-build = { run = "mvn verify", description = "Full build — compile + unit tests + integration tests (requires Docker)" }
validate = { run = "mvn validate", description = "Run Checkstyle code style checks" }
format = { run = "mvn formatter:format", description = "Auto-format Java sources using the project Eclipse formatter" }
[tasks.test]
run = "mvn verify"
description = "Compile + unit tests + integration tests (requires Docker)"

docker-build = { run = "mvn clean package '-Dquarkus.container-image.build=true'", description = "Build the app + Docker image" }
license-gen = { run = "mvn package -Plicense-gen -DskipTests", description = "Generate third-party licenses (Red Hat certification)" }
[tasks.validate]
run = "mvn validate"
description = "Run Checkstyle code style checks"

debug = { run = "mvn quarkus:dev -Dsuspend", description = "Start dev mode and wait for debugger on port 5005" }
dev-no-debug = { run = "mvn quarkus:dev -Ddebug=false", description = "Start dev mode without the debug agent" }
chroma = { run = "docker compose -f docker-compose.chroma.yml up", description = "Starts ChromaDB" }
[tasks.format]
run = "mvn formatter:format"
description = "Auto-format Java sources using the project Eclipse formatter"

[tasks.docker-build]
run = "mvn clean package '-Dquarkus.container-image.build=true'"
description = "Build the app + Docker image"


[tasks.license-gen]
run = "mvn package -Plicense-gen -DskipTests"
description = "Generate third-party licenses (Red Hat certification)"

[tasks.debug]
run = "mvn quarkus:dev -Dsuspend"
description = "Start dev mode and wait for debugger on port 5005"

[tasks.clean]
run = "mvn clean"
description = "Clean build — delete target"


[tasks.chroma]
run = "docker compose -f docker-compose.chroma.yml up"
description = "Starts ChromaDB"

6 changes: 3 additions & 3 deletions planning/memory-architecture-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,9 @@ Changes required:
} catch (Exception e) {
// Failure → mark all NEW data written by this task as uncommitted
markNewDataUncommitted(memory.getCurrentStep(), preTaskData);
auditLedger.logTaskFailure(task.getId(), e, conversationId);
// Inject error action, NOT the exception, into the pipeline
memory.getCurrentStep().storeData(new Data<>("actions", List.of("task_failed_" + task.getId())));
auditLedger.logTaskFailure(task.getId().name(), e, conversationId);
// Inject error action, NOT the exception, into the pipeline
memory.getCurrentStep().storeData(new Data<>("actions", List.of("task_failed_" + task.getId().name())));
}
```

Expand Down
2 changes: 1 addition & 1 deletion planning/observability-and-pipeline-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
The main execution path is in [`LifecycleManager.executeLifecycle()`](../../src/main/java/ai/labs/eddi/engine/lifecycle/LifecycleManager.java). Add span-per-task:

```java
Span span = tracer.spanBuilder("eddi.task." + task.getId())
Span span = tracer.spanBuilder("eddi.task." + task.getId().name())
.setAttribute("conversationId", conversationId)
.setAttribute("agentId", agentId)
.startSpan();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
import java.util.List;
import java.util.Map;

import ai.labs.eddi.engine.lifecycle.TaskId;

public class ExtensionDescriptor {
private String type;
private TaskId type;

private String displayName;
private Map<String, ConfigValue> configs = new HashMap<>();
private Map<String, List<ExtensionDescriptor>> extensions = new HashMap<>();

public ExtensionDescriptor(String type) {
public ExtensionDescriptor(TaskId type) {
this.type = type;
}

Expand Down Expand Up @@ -73,11 +76,11 @@ public void addExtension(String extensionName, ExtensionDescriptor extensionDesc
extensions.computeIfAbsent(extensionName, k -> new LinkedList<>()).add(extensionDescriptor);
}

public String getType() {
public TaskId getType() {
return type;
}

public void setType(String type) {
public void setType(TaskId type) {
this.type = type;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,23 @@
import jakarta.inject.Provider;
import java.util.List;
import java.util.Map;

@ApplicationScoped
public class RestWorkflowStepStore implements IRestWorkflowStepStore {
private final Map<String, Provider<ILifecycleTask>> lifecycleExtensionsProvider;

@Inject
public RestWorkflowStepStore(@LifecycleExtensions Map<String, Provider<ILifecycleTask>> lifecycleExtensionsProvider) {

this.lifecycleExtensionsProvider = lifecycleExtensionsProvider;
}

@Override
public List<ExtensionDescriptor> getWorkflowSteps(String filter) {
return lifecycleExtensionsProvider.keySet().stream().filter(type -> filter.isEmpty() || type.contains(filter)).map(type -> {
Provider<ILifecycleTask> taskProvider = lifecycleExtensionsProvider.get(type);
return taskProvider.get().getExtensionDescriptor();
}).toList();
return lifecycleExtensionsProvider.keySet().stream()
.filter(type -> filter == null || filter.isEmpty() || type.contains(filter))
.map(type -> {
Provider<ILifecycleTask> taskProvider = lifecycleExtensionsProvider.get(type);
return taskProvider.get().getExtensionDescriptor();
}).toList();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import ai.labs.eddi.datastore.IResourceStore.ResourceStoreException;
import ai.labs.eddi.engine.memory.model.SimpleConversationMemorySnapshot;
import ai.labs.eddi.engine.model.Context;
import ai.labs.eddi.engine.lifecycle.TaskId;
import ai.labs.eddi.engine.memory.model.ConversationState;
import ai.labs.eddi.engine.model.Deployment.Environment;
import ai.labs.eddi.engine.model.InputData;
Expand Down Expand Up @@ -105,9 +106,9 @@ interface ConversationResponseHandler {
* Callback for streaming conversation responses via SSE.
*/
interface StreamingResponseHandler {
void onTaskStart(String taskId, String taskType, int index);
void onTaskStart(TaskId taskId, String taskType, int index);

void onTaskComplete(String taskId, String taskType, long durationMs, Map<String, Object> summary);
void onTaskComplete(TaskId taskId, String taskType, long durationMs, Map<String, Object> summary);

void onToken(String token);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import ai.labs.eddi.engine.caching.ICacheFactory;
import ai.labs.eddi.engine.lifecycle.ConversationEventSink;
import ai.labs.eddi.engine.lifecycle.IConversation;
import ai.labs.eddi.engine.lifecycle.TaskId;
import ai.labs.eddi.engine.lifecycle.exceptions.LifecycleException;
import ai.labs.eddi.engine.memory.ConversationLogGenerator;
import ai.labs.eddi.engine.memory.IConversationMemory;
Expand Down Expand Up @@ -420,12 +421,12 @@ public void sayStreaming(Environment environment, String agentId, String convers
// Create event sink that delegates to the streaming handler
var eventSink = new ConversationEventSink() {
@Override
public void onTaskStart(String taskId, String taskType, int index) {
public void onTaskStart(TaskId taskId, String taskType, int index) {
streamingHandler.onTaskStart(taskId, taskType, index);
}

@Override
public void onTaskComplete(String taskId, String taskType, long durationMs, Map<String, Object> summary) {
public void onTaskComplete(TaskId taskId, String taskType, long durationMs, Map<String, Object> summary) {
streamingHandler.onTaskComplete(taskId, taskType, durationMs, summary);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import ai.labs.eddi.engine.api.IRestAgentEngineStreaming;
import ai.labs.eddi.engine.memory.model.SimpleConversationMemorySnapshot;

import ai.labs.eddi.engine.lifecycle.TaskId;
import ai.labs.eddi.engine.model.InputData;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -55,15 +56,16 @@ public void sayStreaming(String conversationId, Boolean returnDetailed, Boolean
conversationService.sayStreaming(conversationId, returnDetailed, returnCurrentStepOnly, returningFields, inputData,
new IConversationService.StreamingResponseHandler() {
@Override
public void onTaskStart(String taskId, String taskType, int index) {
public void onTaskStart(TaskId taskId, String taskType, int index) {
sendEvent(eventSink, sse, "task_start",
String.format("{\"taskId\":\"%s\",\"taskType\":\"%s\",\"index\":%d}", taskId, taskType, index));
String.format("{\"taskId\":\"%s\",\"taskType\":\"%s\",\"index\":%d}", taskId.getIdentifier(), taskType, index));
}

@Override
public void onTaskComplete(String taskId, String taskType, long durationMs, Map<String, Object> summary) {
public void onTaskComplete(TaskId taskId, String taskType, long durationMs, Map<String, Object> summary) {
var sb = new StringBuilder();
sb.append(String.format("{\"taskId\":\"%s\",\"taskType\":\"%s\",\"durationMs\":%d", taskId, taskType, durationMs));
sb.append(String.format("{\"taskId\":\"%s\",\"taskType\":\"%s\",\"durationMs\":%d", taskId.getIdentifier(), taskType,
durationMs));
if (summary.containsKey("actions")) {
sb.append(",\"actions\":").append(toJsonArray(summary.get("actions")));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public interface ConversationEventSink {
* @param index
* 0-based position in the workflow
*/
void onTaskStart(String taskId, String taskType, int index);
void onTaskStart(TaskId taskId, String taskType, int index);

/**
* Called after a lifecycle task completes execution.
Expand All @@ -48,7 +48,7 @@ public interface ConversationEventSink {
* optional task-specific summary data (e.g. emitted actions). May be
* empty, never null.
*/
void onTaskComplete(String taskId, String taskType, long durationMs, Map<String, Object> summary);
void onTaskComplete(TaskId taskId, String taskType, long durationMs, Map<String, Object> summary);

/**
* Called for each LLM token during streaming chat completion.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public interface ILifecycleTask {
* @return unique identifier of this task (e.g., "ai.labs.parser",
* "ai.labs.behavior")
*/
String getId();
TaskId getId();

/**
* Returns the type identifier for this lifecycle task.
Expand Down
Loading
Loading