Skip to content

feat: add compute prop to Task for non-agent deterministic operations #74

@enitrat

Description

@enitrat

Problem

When a workflow step needs to run a deterministic operation (e.g. bun test, tsc --noEmit, data transformation), the only option today is to use a full LLM agent. This wastes tokens, adds latency, and introduces non-determinism for what is fundamentally a compute operation.

Currently, TaskProps supports two modes:

  • Agent mode (when agent is provided): runs an LLM agent with children rendered as the prompt
  • Static mode (when agent is absent): stores children as __smithersPayload — but this is evaluated at render time, not execution time, so async operations aren't possible

There's no way to run an async function during the execution phase without an agent.

Example

// ❌ What we want — async compute during execution
<Task id="validate" output="validate" compute={async () => {
  const testResult = await $\`bun test\`.quiet();
  const typeResult = await $\`tsc --noEmit\`.quiet();
  return {
    testsPass: testResult.exitCode === 0,
    typesPass: typeResult.exitCode === 0,
  };
}} />

// ✅ What we have to do today — use an LLM agent for 2 shell commands
<Task id="validate" output="validate" agent={implementer}>
  Run \`bun test\` and \`tsc --noEmit\` and report results
</Task>

Using an LLM agent to run deterministic shell commands works but is wasteful (tokens, latency, cost).

Proposed Solution

Add a compute prop to TaskProps:

export type TaskProps<Row> = {
  // ... existing props
  compute?: () => Promise<Row>;
};

When compute is present and agent is absent:

  1. Set __smithersKind: "compute" in the component
  2. During the execution phase (in executeTask), call the compute function
  3. Validate the output against the schema
  4. Persist to SQLite as usual

This would give Smithers a three-mode task model:

  • Agent: LLM-driven, non-deterministic
  • Compute: user function, deterministic, async
  • Static: inline data, evaluated at render time

Context

The engine already has infrastructure for this — TaskDescriptor has a staticPayload field, and executeTask handles the case where no agent is present. The compute prop would be a natural extension: store the function reference, call it during execution, and feed the result through the same validation + persistence path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions