Skip to content

feat(workflow-executor): add ReadRecordStepExecutor#1497

Open
Scra3 wants to merge 33 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feat/prd-218-read-record-step-executor
Open

feat(workflow-executor): add ReadRecordStepExecutor#1497
Scra3 wants to merge 33 commits intofeat/prd-214-setup-workflow-executor-packagefrom
feat/prd-218-read-record-step-executor

Conversation

@Scra3
Copy link
Member

@Scra3 Scra3 commented Mar 18, 2026

Summary

  • Add ReadRecordStepExecutor — first AI-task step executor that reads fields from a record
  • AI-based record selection when multiple records are available (via select-record tool)
  • AI-based field selection via read-selected-record-field tool (supports single/multiple fields, displayName + fieldName resolution)
  • New error classes: NoRecordsError, NoReadableFieldsError
  • Relationship fields excluded from readable fields (ISO frontend)
  • Previous steps context included in both record selection and field selection prompts

Test plan

  • Single record, single/multiple field reads
  • Field resolution by displayName
  • Field not found returns per-field error (no global failure)
  • Relationship fields excluded from tool schema
  • No readable fields → error status
  • Multi-record AI selection (first and second record)
  • No records → error status
  • Model error / malformed tool call / missing tool call → error status
  • RunStore errors propagate (getRecords, saveStepExecution)
  • Input stepHistory immutability
  • Previous steps context in messages
  • Default prompt fallback
  • saveStepExecution arguments (executionParams, executionResult, selectedRecordRef)

fixes PRD-218

🤖 Generated with Claude Code

Note

Add ReadRecordStepExecutor for AI-driven record field selection and reading

  • Adds ReadRecordStepExecutor that selects a record (prompting the model when multiple are available), picks relevant fields via a tool call, reads them via agentPort, and persists results with per-field success/error entries.
  • Renames StepHistoryStepOutcome, CollectionRefCollectionSchema, RecordFieldRefFieldSchema, and related types throughout the package to clarify semantics.
  • Removes getActions from AgentPort and trims RunStore to only getStepExecutions/saveStepExecution; WorkflowPort.getCollectionRef is renamed to getCollectionSchema.
  • BaseStepExecutor.buildStepSummary now emits separate Input:/Output: lines when execution data exists, replacing the previous single Result: line.
  • Adds NoRecordsError and NoReadableFieldsError as typed WorkflowExecutorError subclasses, and exports isExecutedStepOnExecutor as a runtime type guard.
  • Risk: RunStore, AgentPort, and WorkflowPort interface removals are breaking changes for any existing implementations outside this package.

Macroscope summarized 650e70b.

@linear
Copy link

linear bot commented Mar 18, 2026

@qltysh
Copy link

qltysh bot commented Mar 18, 2026

Qlty

Coverage Impact

Unable to calculate total coverage change because base branch coverage was not found.

Modified Files with Diff Coverage (8)

RatingFile% DiffUncovered Line #s
New file Coverage rating: A
...s/workflow-executor/src/executors/read-record-step-executor.ts100.0%
New file Coverage rating: A
...ges/workflow-executor/src/executors/condition-step-executor.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/executors/base-step-executor.ts100.0%
New file Coverage rating: A
.../workflow-executor/src/adapters/forest-server-workflow-port.ts100.0%
New file Coverage rating: A
...ages/workflow-executor/src/adapters/agent-client-agent-port.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/types/step-execution-data.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/errors.ts100.0%
New file Coverage rating: A
packages/workflow-executor/src/index.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

Base automatically changed from feat/prd-216-condition-step-executor to feat/prd-214-setup-workflow-executor-package March 18, 2026 13:00
@Scra3 Scra3 force-pushed the feat/prd-218-read-record-step-executor branch from f6b101a to 71c4104 Compare March 18, 2026 13:06
}

export interface RecordData extends RecordRef {
stepIndex: number;
Copy link
Member Author

Choose a reason for hiding this comment

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

want to save the stepIndex to be sure to find the right o step in the workflow

@Scra3 Scra3 force-pushed the feat/prd-218-read-record-step-executor branch from 55b265f to 25fdd06 Compare March 18, 2026 14:41
import type { StepExecutionData } from '../types/step-execution-data';

export interface RunStore {
getRecords(): Promise<RecordData[]>;
Copy link
Member Author

Choose a reason for hiding this comment

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

useless

alban bertolini and others added 20 commits March 18, 2026 18:08
Implement the read-record step executor that allows AI to read fields
from a record. Includes AI-based record selection when multiple records
are available, and field selection via the read-selected-record-field tool.

fixes PRD-218

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…epExecutor

- Extract toRecordIdentifier helper to avoid duplicate template string
- Add test: AI selects non-existent record identifier
- Add saveStepExecution not-called assertions to no-records and no-readable-fields tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ce symmetry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…to single FieldReadResult

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…elds across FieldRead types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…c method

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… array schema

- Remove unused FieldReadResults type alias
- Rename read-selected-record-field to read-selected-record-fields
- fieldNames is now always string[] (no more single string union)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move FieldReadResult types from executor to step-execution-data.ts
- Add ReadRecordStepExecutionData with typed executionParams/executionResult
- Use type: 'read-record' discriminator instead of generic 'ai-task'
- Export new types from index.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…equired

- executionParams and executionResult are now required (align with ReadRecord)
- Reorganize step-execution-data.ts by section (Condition, ReadRecord, Generic)
- Fix base-step-executor tests for required fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused fieldNames variable in buildReadFieldTool
- Fix prettier formatting
- Fix duplicate test title in base-step-executor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ypes

Split the old CollectionRef → RecordData hierarchy into independent
schema types (CollectionSchema, FieldSchema) and data types (RecordRef,
RecordData). Schema is now sourced from WorkflowPort.getCollectionSchema,
records from AgentPort/RunStore. Remove dead fields (type,
referencedCollectionName) and dead code (availableRecords).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ove AgentPort.getActions

Actions metadata is structural info belonging to CollectionSchema, not
an AgentPort concern. Remove the redundant getActions method from
AgentPort and rename ActionRef to ActionSchema for naming consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove getRecord, saveRecord, and getStepExecution from RunStore
interface — none are called in production code. Records are accessed
via getRecords, step executions via getStepExecutions/saveStepExecution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ord + LoadRelatedRecordStepExecutionData

Records are now accessed via ExecutionContext.baseRecord (trigger record) and
LoadRelatedRecordStepExecutionData entries in the RunStore, removing the
separate getRecords() method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add isExecutedStepOnExecutor type guard and ExecutedStepExecutionData
union type. buildStepSummary now outputs executionResult so subsequent
steps can access field values read by earlier steps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
executionParams is the input (AI decision), executionResult is the
output. Rename the summary label accordingly for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The field holds a RecordRef, not a RecordData. Rename for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
alban bertolini and others added 3 commits March 18, 2026 18:08
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ues via AgentPort

The orchestrator only sends a record reference (collection + recordId), not client data.
ReadRecordStepExecutor now fetches values through agentPort.getRecord() when needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Scra3 Scra3 force-pushed the feat/prd-218-read-record-step-executor branch from 35a89ec to 33e5552 Compare March 18, 2026 17:12
alban bertolini and others added 3 commits March 18, 2026 18:40
…ta type

- Only catch WorkflowExecutorError in execute(), re-throw infrastructure errors
- Redefine RecordData as Omit<RecordRef, 'stepIndex'> & { values }
- Change LoadRelatedRecordStepExecutionData.record to RecordRef
- Use RecordData directly in AgentPort (remove AgentRecord alias)
- Update tests: model errors propagate, add RecordNotFoundError test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Context

Make ExecutionContext generic with TStep/THistory defaults so executors
read step and stepHistory from context instead of execute() arguments.
Also rename selectRecord → selectRecordRef and getAvailableRecords →
getAvailableRecordRefs for consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename agentRecord → recordData and readFieldValues → formatFieldResults
for clarity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fieldNames: string[],
): FieldReadResult[] {
return fieldNames.map(name => {
const field = schema.fields.find(f => f.fieldName === name || f.displayName === name);
Copy link

Choose a reason for hiding this comment

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

🟡 Medium executors/read-record-step-executor.ts:167

In formatFieldResults, the field lookup uses f.fieldName === name || f.displayName === name, checking fieldName before displayName. Since the AI tool in buildReadFieldTool is told to use displayNames as possible values, a field with displayName = "Status" could be shadowed by another field with fieldName = "Status", causing the wrong field's value to be returned.

-      const field = schema.fields.find(f => f.fieldName === name || f.displayName === name);
+      const field = schema.fields.find(f => f.displayName === name || f.fieldName === name);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/executors/read-record-step-executor.ts around line 167:

In `formatFieldResults`, the field lookup uses `f.fieldName === name || f.displayName === name`, checking `fieldName` before `displayName`. Since the AI tool in `buildReadFieldTool` is told to use `displayNames` as possible values, a field with `displayName = "Status"` could be shadowed by another field with `fieldName = "Status"`, causing the wrong field's value to be returned.

Evidence trail:
packages/workflow-executor/src/executors/read-record-step-executor.ts lines 134-159 (buildReadFieldTool telling AI to use displayNames) and lines 161-176 (formatFieldResults using `f.fieldName === name || f.displayName === name`); packages/workflow-executor/src/types/record.ts lines 5-9 (FieldSchema with separate fieldName and displayName).

Copy link
Member Author

Choose a reason for hiding this comment

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

Not fixed — both lookup orders have the same shadowing risk in opposite directions. The current order is ISO frontend. In practice, fieldName and displayName are almost never the same string across different fields, so the collision scenario is theoretical.

alban bertolini and others added 7 commits March 18, 2026 19:49
…ep summary

Add null check before stringifying executionParams in buildStepSummary,
since AiTaskStepExecutionData.executionParams is optional. Also fix
prettier issue on selectRecordRef signature.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ils import

ServerUtils is now properly exported from @forestadmin/forestadmin-client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test for undefined executionParams in step summary and instantiate
NoRecordsError to meet diff coverage threshold.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pExecution

Replace separate step/stepHistory fields with currentStep: StepRecord
for consistency with the existing StepRecord type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… and add stepIndex to StepDefinition

Executors now construct StepHistory internally instead of receiving it
via context. Also removes unused StepCategory type and simplifies
selectedRecordRef spread.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ed tool func callbacks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tract id/stepIndex from StepDefinition

StepHistory → StepOutcome for clarity (it's the outcome of a step, not history).
Moved id/stepIndex from StepDefinition (definition concern) to ExecutionContext
and PendingStepExecution (instance concern). Renamed StepRecord to Step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

1 participant