Skip to content

fix: resolve structured action context inputs - #229

Open
Optic00 wants to merge 1 commit into
Windshiftapp:mainfrom
Optic00:codex/fix-action-ai-context
Open

fix: resolve structured action context inputs#229
Optic00 wants to merge 1 commit into
Windshiftapp:mainfrom
Optic00:codex/fix-action-ai-context

Conversation

@Optic00

@Optic00 Optic00 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Centralize Action execution-context lookup across templates, ai_extract, and ai_agent.
  • Resolve editor-provided item.* inputs, nested output paths, slices, trigger data, prior values, users, and SCM payloads.
  • Preserve tolerant template placeholders while making missing configured AI inputs fail with a clear error.
  • Serialize structured template and extraction values as JSON.

Behavior note

ai_agent previously skipped unresolved configured inputs and could run with a partial or empty prompt. It now fails the node before starting an LLM call. Present null values remain valid inputs.

Tests

  • go test ./internal/services
  • go test ./... after satisfying the required frontend/dist embed input

This PR includes focused public unit coverage. Test placement is also being discussed in #193; I am happy to adapt it to the maintainers preferred convention.

@stefan-ernst
stefan-ernst self-requested a review August 29, 2026 12:57
@stefan-ernst

Copy link
Copy Markdown
Contributor

Finally had time to do a proper review with the core-tests repo being public. There are only 4 failures that should be resolved when rebasing this PR.

Only one finding remained:

  1. [P1] Top-level actions cannot resolve several advertised item.* or any user.* inputs.

    executeAction
    (

    ctx := &models.ExecutionContext{
    Action: action,
    Event: event,
    EffectiveActorID: effectiveActorID,
    Variables: make(map[string]any),
    StepResults: []models.StepResult{},
    ChainID: chainID,
    }
    // Expose the effective actor to template expansion.
    ctx.Variables["item_id"] = event.ItemID
    ctx.Variables["workspace_id"] = event.WorkspaceID
    ctx.Variables["actor_user_id"] = effectiveActorID
    ctx.Variables["trigger_user_id"] = event.ActorUserID
    for k, v := range event.OldValues {
    ctx.Variables["old_"+k] = v
    }
    for k, v := range event.NewValues {
    ctx.Variables["new_"+k] = v
    }
    )
    creates an ExecutionContext without populating Item or Actor. The new resolveExecutionValue
    (
    case "item":
    value, ok := currentItemFieldValueResolved(as.itemRepo, ctx, parts[1])
    if !ok {
    return nil, false
    }
    return nestedExecutionValue(value, parts[2:])
    case "trigger":
    if value, ok := ctx.Variables[parts[1]]; ok {
    return nestedExecutionValue(value, parts[2:])
    }
    return nil, false
    case "old":
    if value, ok := ctx.Variables["old_"+parts[1]]; ok {
    return nestedExecutionValue(value, parts[2:])
    }
    return nil, false
    case "user":
    if ctx.Actor != nil && len(parts) == 2 {
    switch parts[1] {
    case "name":
    return ctx.Actor.FirstName + " " + ctx.Actor.LastName, true
    case "email":
    return ctx.Actor.Email, true
    case "id":
    return ctx.Actor.ID, true
    }
    }
    return nil, false
    )
    requires those fields for:

    • item.id
    • item.status
    • item.priority
    • item.custom_field_*
    • every user.* input

    ctx.Item is populated only inside iterator execution; ctx.Actor is never populated. A production-shaped
    diagnostic using Event.ItemID=42 and EffectiveActorID=7 reproduced:

    item.id -> (, false)
    user.id -> (, false)

    This is user-visible because the editor explicitly suggests item.id, item.status, and item.priority, while
    buildAIAgentUserMessage
    (

    func (as *ActionService) buildAIAgentUserMessage(ctx *models.ExecutionContext, fields []string) (string, error) {
    inputParts := make([]string, 0, len(fields))
    for _, field := range fields {
    value, ok := as.resolveExecutionValue(ctx, field)
    if !ok {
    return "", fmt.Errorf("input field %q not found in execution context", field)
    }
    )
    now hard-fails when any configured input is missing. Valid editor-created agent actions therefore abort
    before invoking the LLM.

    The context should be hydrated from the event/effective actor, or the resolver should fall back through the
    repositories and event IDs.

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