Skip to content

Fix/ExtensionEndpoint#505

Merged
ginccc merged 2 commits into
labsai:mainfrom
niedch:eddi-extension-fix
Jun 1, 2026
Merged

Fix/ExtensionEndpoint#505
ginccc merged 2 commits into
labsai:mainfrom
niedch:eddi-extension-fix

Conversation

@niedch

@niedch niedch commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Frontend branch PR is labsai/EDDI-Manager#85

Introduces a TaskId record to replace raw String identifiers for lifecycle tasks, making task identity type-safe and canonical. Also renames the record field from typename to avoid confusion with ILifecycleTask.getType() which returns a different value.

The /extensionstore/extensions endpoint was returning bare strings like "ai.labs.parser" in its type field, but should always have returned the full eddi:// URI format (e.g., "eddi://ai.labs.parser") to comply with the MSW. This was an existing bug — the workflow step type is defined as a URI throughout the config model (WorkflowStep.type is java.net.URI), and the extension descriptor's type field should have been consistent with that. This PR fixes it by wrapping the value in TaskId, which serializes to the canonical URI format.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)

Related Issue

Closes #

Changes Made

  • New TaskId record (ai.labs.eddi.engine.lifecycle.TaskId) — immutable wrapper with canonical URI format (eddi://ai.labs.parser). Serializes as full URI via @JsonValue, deserializes from both "eddi://ai.labs.parser" and bare "ai.labs.parser" via @JsonCreator for backward compatibility.
  • ILifecycleTask.getId() return type changed from StringTaskId. All 8 task implementations updated with static final TaskId TASK_ID constant.
  • ExtensionDescriptor.type changed from StringTaskId. Constructor, getter, and setter updated; all task getExtensionDescriptor() calls now pass new TaskId(ID). This fixes the /extensionstore/extensions endpoint to return the canonical eddi:// URI format as required by the MSW.
  • SSE streaming interfaces (ConversationEventSink, IConversationService.StreamingResponseHandler) — taskId parameter type changed from StringTaskId. RestAgentEngineStreaming uses taskId.getIdentifier() for JSON output.
  • LifecycleManager — 8 call sites updated to use task.getId().name() for bare identifier extraction (metrics, cache keys, audit entries, tracing).
  • WorkflowStoreClientLibrary — component cache key uses .name().
  • Field rename TaskId.typeTaskId.name — eliminates naming clash with ILifecycleTask.getType() which returns a pipeline stage name (e.g., "expressions"), not the task identifier.

How to Test

  1. ./mvnw compile — confirms all type changes compile
  2. ./mvnw test -pl . -Dtest="InputParserTaskTest,RulesEvaluationTaskTest,ApiCallsTaskTest,PropertySetterTaskTest,OutputGenerationTaskTest,LlmTaskTest,McpCallsTaskTest" — verifies all getId() and getExtensionDescriptor() assertions
  3. GET /extensionstore/extensions — verify that type fields serialize as "eddi://ai.labs.parser" (not bare "ai.labs.parser"), matching the MSW URI convention
  4. POST /conversation/{id}/say with SSE — verify task_start and task_complete events contain "taskId": "eddi://ai.labs.parser" format
  5. Import an existing agent config ZIP — verify deserialization still accepts bare strings in WorkflowStep.type via TaskId.fromValue() backward compat

Checklist

  • My code follows the project's code style
  • I have added tests that prove my fix/feature works
  • Existing tests pass locally (./mvnw clean verify -DskipITs)
  • I have updated documentation if needed
  • My commit messages follow conventional commits
  • I have not committed any secrets, API keys, or tokens
  • This PR has a clear, focused scope (one concern per PR)

Breaking change details for reviewers:

What changed Before After Impact
GET /extensionstore/extensions type field "ai.labs.parser" "eddi://ai.labs.parser" EDDI-Manager and any API clients parsing typebug fix for MSW compliance
SSE task_start/task_complete taskId field "ai.labs.parser" "eddi://ai.labs.parser" SSE event consumers
ILifecycleTask.getId() returns String returns TaskId Binary incompatible for external task implementations (none exist outside this repo)

Backward compat: TaskId.fromValue(@JsonCreator) accepts both "eddi://ai.labs.parser" and bare "ai.labs.parser" on deserialization, so existing agent configs and workflow step definitions continue to work without changes.

Summary by CodeRabbit

  • Documentation

    • Updated architecture, developer quickstart, agent, and planning docs and examples to reflect the new lifecycle task identifier format.
  • Refactor

    • Standardized lifecycle task identifiers across the system.
    • Enhanced observability/span naming and updated streaming callbacks to use structured task identifiers.
    • Added runtime guards to validate task identifiers early.
  • Chores

    • Added chromadb tool and reorganized build task configuration.

Review Change Stack

- so its clear if the full URI is the type or only the identifier needs to be used
@niedch
niedch requested review from ginccc and rolandpickl as code owners May 27, 2026 10:43
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2a838864-bd1c-43c7-99c9-066b4eeb309a

📥 Commits

Reviewing files that changed from the base of the PR and between 937ef5f and c649042.

📒 Files selected for processing (9)
  • AGENTS.md
  • planning/memory-architecture-plan.md
  • planning/observability-and-pipeline-plan.md
  • src/main/java/ai/labs/eddi/configs/workflows/rest/RestWorkflowStepStore.java
  • src/main/java/ai/labs/eddi/engine/lifecycle/TaskId.java
  • src/main/java/ai/labs/eddi/engine/lifecycle/internal/LifecycleManager.java
  • src/main/java/ai/labs/eddi/engine/runtime/client/workflows/WorkflowStoreClientLibrary.java
  • src/main/java/ai/labs/eddi/modules/rules/impl/RulesEvaluationTask.java
  • src/main/java/ai/labs/eddi/modules/templating/OutputTemplateTask.java
✅ Files skipped from review due to trivial changes (2)
  • src/main/java/ai/labs/eddi/configs/workflows/rest/RestWorkflowStepStore.java
  • planning/memory-architecture-plan.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • AGENTS.md
  • src/main/java/ai/labs/eddi/engine/runtime/client/workflows/WorkflowStoreClientLibrary.java
  • planning/observability-and-pipeline-plan.md
  • src/main/java/ai/labs/eddi/modules/templating/OutputTemplateTask.java
  • src/main/java/ai/labs/eddi/engine/lifecycle/TaskId.java
  • src/main/java/ai/labs/eddi/engine/lifecycle/internal/LifecycleManager.java

📝 Walkthrough

Walkthrough

Introduce TaskId record and migrate lifecycle identifiers from String to TaskId across interfaces, event/streaming handlers, task implementations, observability, cache keys, tests, and related docs/config.

Changes

TaskId Type & Contract Migration

Layer / File(s) Summary
TaskId definition & ILifecycleTask contract
src/main/java/ai/labs/eddi/engine/lifecycle/TaskId.java, src/main/java/ai/labs/eddi/engine/lifecycle/ILifecycleTask.java
New immutable TaskId record with JSON (de)serialization, canonical eddi://<name> identifier; ILifecycleTask#getId() now returns TaskId.
ExtensionDescriptor & public callbacks
src/main/java/ai/labs/eddi/configs/workflows/model/ExtensionDescriptor.java, src/main/java/ai/labs/eddi/engine/lifecycle/ConversationEventSink.java, src/main/java/ai/labs/eddi/engine/api/IConversationService.java
ExtensionDescriptor.type and its accessors now use TaskId. Conversation streaming callbacks accept TaskId instead of String.
Docs & Examples
AGENTS.md, docs/architecture.md, docs/developer-quickstart.md
Documentation and example tasks updated to construct and return TaskId from getId() and to pass getId() into ExtensionDescriptor.

Streaming & Event Infrastructure

Layer / File(s) Summary
ConversationService streaming sink
src/main/java/ai/labs/eddi/engine/internal/ConversationService.java
sayStreaming() forwards TaskId parameters to StreamingResponseHandler callbacks.
RestAgentEngineStreaming SSE handling
src/main/java/ai/labs/eddi/engine/internal/RestAgentEngineStreaming.java
SSE handler accepts TaskId for task events and serializes using taskId.getIdentifier().

Lifecycle Task Implementation Updates

Layer / File(s) Summary
Task classes migrated to TaskId
src/main/java/ai/labs/eddi/modules/.../*Task.java
Task classes add public static final TaskId TASK_ID, return TaskId from getId(), and construct ExtensionDescriptor with TaskId. (ApiCallsTask, LlmTask, McpCallsTask, InputParserTask, OutputGenerationTask, PropertySetterTask, RulesEvaluationTask, OutputTemplateTask)

LifecycleManager & Storage Integration

Layer / File(s) Summary
LifecycleManager observability & keys
src/main/java/ai/labs/eddi/engine/lifecycle/internal/LifecycleManager.java
Fail-fast if task.getId() is null; use task.getId().name() for OpenTelemetry span names/attributes, Micrometer labels, component-cache lookup keys, LangChain trace keys, audit entries, and strict-write failure artifacts.
WorkflowStoreClientLibrary cache key
src/main/java/ai/labs/eddi/engine/runtime/client/workflows/WorkflowStoreClientLibrary.java
Cache key changed to lifecycleTask.getId().name() and null-check added.
Planning docs
planning/memory-architecture-plan.md, planning/observability-and-pipeline-plan.md
Updated pseudo-code/examples to use task.getId().name() in audit/action and span naming examples.

Testing & Configuration Updates

Layer / File(s) Summary
Lifecycle & streaming tests
src/test/java/ai/labs/eddi/engine/.../LifecycleManager*.java, src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineStreamingExtendedTest.java
Mocks and verifications updated to use new TaskId("<id>"); event sink assertions expect TaskId parameters; sequencing and duration verifications adjusted.
Task tests
src/test/java/ai/labs/eddi/modules/.../*TaskTest.java
Assertions updated to compare .name() for task ids and descriptor types.
Build config
mise.toml
Add npm:chromadb tool entry; refactor tasks table into per-task subtables and update task commands/descriptions (add clean, chroma; change test to mvn verify, etc.).

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • labsai/EDDI#424: Related lifecycle observability changes touching LifecycleManager span/metric instrumentation.

Suggested reviewers

  • rolandpickl

Poem

🐰 A rabbit found a TaskId bright,

It wrapped each task in tidy light,
No more strings to twist and twine,
Now identifiers align—
Hopping through tests, docs, and night.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix/ExtensionEndpoint' is vague and does not clearly communicate the main change, which is introducing a structured TaskId type to replace String identifiers across the codebase. Provide a more descriptive title that highlights the core change, such as 'Introduce TaskId record to replace string task identifiers' or 'Replace String taskId with structured TaskId value object'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
src/main/java/ai/labs/eddi/modules/rules/impl/RulesEvaluationTask.java (1)

198-198: ⚡ Quick win

Reuse TASK_ID when building ExtensionDescriptor.

Line 198 recreates new TaskId(ID) instead of using the existing constant. Using TASK_ID keeps a single source of truth for task identity.

Proposed change
-        var extensionDescriptor = new ExtensionDescriptor(new TaskId(ID));
+        var extensionDescriptor = new ExtensionDescriptor(TASK_ID);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/ai/labs/eddi/modules/rules/impl/RulesEvaluationTask.java` at
line 198, Replace the recreated TaskId instance with the existing TASK_ID
constant when constructing the ExtensionDescriptor to avoid duplicating the task
identity; locate the ExtensionDescriptor creation in RulesEvaluationTask (the
line that currently does new ExtensionDescriptor(new TaskId(ID))) and change it
to use TASK_ID so ExtensionDescriptor receives the single source-of-truth
TaskId.
src/main/java/ai/labs/eddi/modules/templating/OutputTemplateTask.java (1)

207-207: ⚡ Quick win

Use TASK_ID instead of creating a new TaskId.

Line 207 duplicates task-id construction. Reusing TASK_ID avoids divergence if ID handling changes later.

Proposed change
-        ExtensionDescriptor extensionDescriptor = new ExtensionDescriptor(new TaskId(ID));
+        ExtensionDescriptor extensionDescriptor = new ExtensionDescriptor(TASK_ID);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/ai/labs/eddi/modules/templating/OutputTemplateTask.java` at
line 207, In OutputTemplateTask replace the duplicated TaskId construction by
using the existing TASK_ID constant: change the ExtensionDescriptor creation
from new ExtensionDescriptor(new TaskId(ID)) to new ExtensionDescriptor(TASK_ID)
so the class reuses the canonical TASK_ID (avoid creating a new TaskId
instance).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Around line 444-445: The example was updated to return a TaskId from getId(),
but getExtensionDescriptor() still constructs an ExtensionDescriptor with the
old signature; update getExtensionDescriptor() to create the descriptor using a
TaskId (e.g., pass new TaskId(ID) or the getId() result) so the
ExtensionDescriptor constructor matches the new TaskId type; adjust any
references to ID/KEY_MYFEATURE accordingly to use TaskId where required.

In `@planning/memory-architecture-plan.md`:
- Around line 330-333: Replace the outdated call task.getId().type() with the
updated TaskId field accessor per the new TaskId API (e.g., task.getId().type or
task.getId().getType() depending on whether the API exposes a public field or a
getter) in both places: the auditLedger.logTaskFailure(...) call and the
"task_failed_..." string passed into memory.getCurrentStep().storeData(...),
ensuring you reference the current TaskId contract rather than the removed
.type() method.

In `@planning/observability-and-pipeline-plan.md`:
- Line 34: Update the tracing example to stop calling the deprecated TaskId
accessor: replace the use of task.getId().type() inside the tracer.spanBuilder
call with the current identifier accessor (e.g. task.getId().name() or
task.getId().getName(), depending on your TaskId API). Keep the surrounding
expression Span span = tracer.spanBuilder("eddi.task." + ...) and only change
the TaskId accessor so the span name uses the new name/identifier method.

In
`@src/main/java/ai/labs/eddi/configs/workflows/rest/RestWorkflowStepStore.java`:
- Around line 29-34: The listing currently calls filter.isEmpty() which throws
when filter is null; update the stream filter to treat a null or empty filter as
"no filter" by checking (filter == null || filter.isEmpty()) before calling
type.contains(filter), e.g. in the method that builds the return from
lifecycleExtensionsProvider.keySet().stream() (referencing
lifecycleExtensionsProvider, Provider<ILifecycleTask>, and
getExtensionDescriptor()) so callers passing a null query param no longer cause
an NPE.

In `@src/main/java/ai/labs/eddi/engine/lifecycle/internal/LifecycleManager.java`:
- Line 239: The code dereferences task.getId() before calling .name() (e.g., the
.setAttribute call in LifecycleManager where you currently use
Objects.requireNonNullElse(task.getId().name(), "unknown")); change these sites
to null-guard task.getId() first and use a safe extractor such as
Optional.ofNullable(task.getId()).map(TaskId::name).orElse("unknown") or a
ternary guard (task.getId() != null ? task.getId().name() : "unknown") so the
attribute always receives a non-null string; apply the same fix to every
occurrence that currently calls task.getId().name() (lines noted in the review).

In `@src/main/java/ai/labs/eddi/engine/lifecycle/TaskId.java`:
- Around line 45-46: TaskId currently allows blank or malformed names; add
validation in the TaskId canonical constructor to reject null/blank or invalid
formats (throw IllegalArgumentException) and normalize input (e.g., trim) so
instances always have a non-empty valid name; also update any deserialization
path (implement or update TaskId deserializer or a static factory like
TaskId.fromString used by Jackson) to perform the same validation/normalization
so deserialized objects cannot carry invalid values — reference the TaskId
record, its canonical constructor, and the deserialization factory/class (e.g.,
TaskId.fromString or TaskIdDeserializer) when making the changes.

In
`@src/main/java/ai/labs/eddi/engine/runtime/client/workflows/WorkflowStoreClientLibrary.java`:
- Line 87: The code calls componentCache.put(lifecycleTask.getId().name(),
componentKey, component) without validating lifecycleTask.getId(); add a
null-check for lifecycleTask.getId() before deriving the cache key and handle it
by throwing a clear, descriptive exception or logging a configuration/extension
error (e.g., including lifecycleTask and extension identifiers) so
initialization fails with an informative message instead of an NPE; update the
same block where componentCache.put(...) is invoked (referencing
lifecycleTask.getId() and componentCache.put) to perform this validation and
error handling.

---

Nitpick comments:
In `@src/main/java/ai/labs/eddi/modules/rules/impl/RulesEvaluationTask.java`:
- Line 198: Replace the recreated TaskId instance with the existing TASK_ID
constant when constructing the ExtensionDescriptor to avoid duplicating the task
identity; locate the ExtensionDescriptor creation in RulesEvaluationTask (the
line that currently does new ExtensionDescriptor(new TaskId(ID))) and change it
to use TASK_ID so ExtensionDescriptor receives the single source-of-truth
TaskId.

In `@src/main/java/ai/labs/eddi/modules/templating/OutputTemplateTask.java`:
- Line 207: In OutputTemplateTask replace the duplicated TaskId construction by
using the existing TASK_ID constant: change the ExtensionDescriptor creation
from new ExtensionDescriptor(new TaskId(ID)) to new ExtensionDescriptor(TASK_ID)
so the class reuses the canonical TASK_ID (avoid creating a new TaskId
instance).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89d719b3-5671-4f12-ab6a-8278ddd08bb7

📥 Commits

Reviewing files that changed from the base of the PR and between 2f54152 and 937ef5f.

📒 Files selected for processing (36)
  • AGENTS.md
  • docs/architecture.md
  • docs/developer-quickstart.md
  • mise.toml
  • planning/memory-architecture-plan.md
  • planning/observability-and-pipeline-plan.md
  • src/main/java/ai/labs/eddi/configs/workflows/model/ExtensionDescriptor.java
  • src/main/java/ai/labs/eddi/configs/workflows/rest/RestWorkflowStepStore.java
  • src/main/java/ai/labs/eddi/engine/api/IConversationService.java
  • src/main/java/ai/labs/eddi/engine/internal/ConversationService.java
  • src/main/java/ai/labs/eddi/engine/internal/RestAgentEngineStreaming.java
  • src/main/java/ai/labs/eddi/engine/lifecycle/ConversationEventSink.java
  • src/main/java/ai/labs/eddi/engine/lifecycle/ILifecycleTask.java
  • src/main/java/ai/labs/eddi/engine/lifecycle/TaskId.java
  • src/main/java/ai/labs/eddi/engine/lifecycle/internal/LifecycleManager.java
  • src/main/java/ai/labs/eddi/engine/runtime/client/workflows/WorkflowStoreClientLibrary.java
  • src/main/java/ai/labs/eddi/modules/apicalls/impl/ApiCallsTask.java
  • src/main/java/ai/labs/eddi/modules/llm/impl/LlmTask.java
  • src/main/java/ai/labs/eddi/modules/mcpcalls/impl/McpCallsTask.java
  • src/main/java/ai/labs/eddi/modules/nlp/InputParserTask.java
  • src/main/java/ai/labs/eddi/modules/output/impl/OutputGenerationTask.java
  • src/main/java/ai/labs/eddi/modules/properties/impl/PropertySetterTask.java
  • src/main/java/ai/labs/eddi/modules/rules/impl/RulesEvaluationTask.java
  • src/main/java/ai/labs/eddi/modules/templating/OutputTemplateTask.java
  • src/test/java/ai/labs/eddi/engine/internal/RestAgentEngineStreamingExtendedTest.java
  • src/test/java/ai/labs/eddi/engine/lifecycle/LifecycleManagerTest.java
  • src/test/java/ai/labs/eddi/engine/lifecycle/internal/LifecycleManagerStreamingTest.java
  • src/test/java/ai/labs/eddi/engine/lifecycle/internal/LifecycleManagerTest.java
  • src/test/java/ai/labs/eddi/modules/apicalls/impl/ApiCallsTaskTest.java
  • src/test/java/ai/labs/eddi/modules/llm/impl/LlmTaskTest.java
  • src/test/java/ai/labs/eddi/modules/mcpcalls/McpCallsTaskTest.java
  • src/test/java/ai/labs/eddi/modules/nlp/InputParserTaskTest.java
  • src/test/java/ai/labs/eddi/modules/output/OutputGenerationTaskTest.java
  • src/test/java/ai/labs/eddi/modules/properties/PropertySetterTaskTest.java
  • src/test/java/ai/labs/eddi/modules/properties/impl/PropertySetterTaskTest.java
  • src/test/java/ai/labs/eddi/modules/rules/impl/RulesEvaluationTaskTest.java

Comment thread AGENTS.md
Comment thread planning/memory-architecture-plan.md Outdated
Comment thread planning/observability-and-pipeline-plan.md Outdated
Comment thread src/main/java/ai/labs/eddi/engine/lifecycle/internal/LifecycleManager.java Outdated
Comment thread src/main/java/ai/labs/eddi/engine/lifecycle/TaskId.java
@ginccc
ginccc merged commit b136419 into labsai:main Jun 1, 2026
19 of 20 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants