Skip to content

✨ feat(workflow): freeze a goal into a reusable, schedulable workflow#599

Open
vaayne wants to merge 3 commits into
mainfrom
feat/594-freeze-goal-workflow
Open

✨ feat(workflow): freeze a goal into a reusable, schedulable workflow#599
vaayne wants to merge 3 commits into
mainfrom
feat/594-freeze-goal-workflow

Conversation

@vaayne

@vaayne vaayne commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

What

Freeze a once-accepted composite goal's decomposition plan into a reusable, schedulable workflow. Instantiating it skips the planner and deterministically rebuilds the goal subtree, reusing the existing execution / acceptance(contract) / rollup / scheduler infrastructure.

Freeze is per-node (a spectrum, not a switch): a composite node carrying a frozen subplan executes deterministically; a composite node without one is left to the planner ("semi-frozen").

Why

A good decomposition is earned the expensive way — through planning and rework. For a recurring objective whose plan you already trust, re-planning each time wastes tokens and wall-clock. A workflow captures the earned plan so the next run skips straight to acceptance, and can be put on a schedule. Closes #594.

How

  • Data modelagent_workflow table (owner coupling + system workflow_key partial-unique); sched_job.dispatch_kind, sched_job_run.root_goal_id.
  • internal/goalFrozenPlan DTO (distinct from the planner's DecompositionContent), SnapshotFrozenPlan (recursive read), InstantiateFrozen (single-tx recursive materialize → activate), ValidateFrozenPlan. Single-transaction instantiation avoids the dispatcher racing in to plan a frozen composite committed as draft.
  • internal/workflow — entity service: Create (hand-authored), SaveGoalAsWorkflow (source must be an accepted composite owned by caller), Instantiate, list/get/update/delete (owner-scoped).
  • schedulerdispatch_kind=workflow branch instantiates the frozen workflow and records the new root goal on the run via a context sink; AddWorkflowJob is the on-ramp (the dispatch branch would be dead code without it).
  • HTTP (OpenAPI-first)/api/workflows CRUD, …/instantiate, …/schedule, /api/goals/{id}/save-as-workflow.
  • CLIstella workflow (wf): save-as / instantiate(run) / schedule / create / list / get / update / delete.
  • Docs + skills — goal-model explanation, how-to, system prompt, tasks skill reference (EN + ZH).

Verification: mise run format && mise run build && mise run test green; new tests in internal/goal, internal/workflow, internal/scheduler (incl. workflow-job restart round-trip).

Deferred (not v1, per issue)

agent-authority adversarial voting panel · dependency minimization · system-package workflow_key late-binding · UI edge editor.

Refs

Freeze the decomposition of an accepted composite goal into a reusable
entity. Instantiating it skips the planner and deterministically rebuilds
the subtree, reusing the existing execution / acceptance / rollup /
scheduler machinery. Freeze is per-node: a composite carrying a frozen
subplan runs deterministically; one without is left to the planner
(semi-frozen).

- data model: agent_workflow table; sched_job.dispatch_kind +
  sched_job_run.root_goal_id
- internal/goal: FrozenPlan DTO, SnapshotFrozenPlan, InstantiateFrozen
  (single-tx recursive materialize), ValidateFrozenPlan
- internal/workflow: entity service (Create / SaveGoalAsWorkflow /
  Instantiate / list / get / update / delete)
- scheduler: dispatch_kind=workflow branch + AddWorkflowJob on-ramp,
  records root goal on the run
- HTTP: /api/workflows CRUD, instantiate, schedule,
  /api/goals/{id}/save-as-workflow (OpenAPI-first)
- CLI: stella workflow (save-as / instantiate / schedule / create /
  list / get / update / delete)
- docs+skills: goal-model, how-to, system prompt, tasks reference (EN+ZH)

Refs #594
@github-actions

Copy link
Copy Markdown

📊 Coverage Report

Total coverage: 48.4% (generated files excluded)

Lowest-covered entries (first 200)
github.com/CherryHQ/stella/api/client/helpers.go:103:        0.0%
github.com/CherryHQ/stella/api/client/helpers.go:111:        0.0%
github.com/CherryHQ/stella/api/client/helpers.go:17:         0.0%
github.com/CherryHQ/stella/api/client/helpers.go:27:         0.0%
github.com/CherryHQ/stella/api/client/helpers.go:35:         0.0%
github.com/CherryHQ/stella/api/client/helpers.go:58:         0.0%
github.com/CherryHQ/stella/api/client/helpers.go:70:         0.0%
github.com/CherryHQ/stella/api/client/helpers.go:89:         0.0%
github.com/CherryHQ/stella/cmd/stella/commands.go:9:         0.0%
github.com/CherryHQ/stella/cmd/stella/db.go:14:              0.0%
github.com/CherryHQ/stella/cmd/stella/db.go:24:              0.0%
github.com/CherryHQ/stella/cmd/stella/email.go:103:          0.0%
github.com/CherryHQ/stella/cmd/stella/email.go:815:          0.0%
github.com/CherryHQ/stella/cmd/stella/email.go:828:          0.0%
github.com/CherryHQ/stella/cmd/stella/email.go:82:           0.0%
github.com/CherryHQ/stella/cmd/stella/email.go:836:          0.0%
github.com/CherryHQ/stella/cmd/stella/email.go:844:          0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:145:           0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:184:           0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:222:           0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:22:            0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:258:           0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:292:           0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:332:           0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:48:            0.0%
github.com/CherryHQ/stella/cmd/stella/goal.go:99:            0.0%
github.com/CherryHQ/stella/cmd/stella/main.go:11:            0.0%
github.com/CherryHQ/stella/cmd/stella/mise.go:13:            0.0%
github.com/CherryHQ/stella/cmd/stella/mise.go:29:            0.0%
github.com/CherryHQ/stella/cmd/stella/oauth.go:102:          0.0%
github.com/CherryHQ/stella/cmd/stella/oauth.go:139:          0.0%
github.com/CherryHQ/stella/cmd/stella/oauth.go:14:           0.0%
github.com/CherryHQ/stella/cmd/stella/oauth.go:31:           0.0%
github.com/CherryHQ/stella/cmd/stella/oauth.go:68:           0.0%
github.com/CherryHQ/stella/cmd/stella/recally_articles.go:403: 0.0%
github.com/CherryHQ/stella/cmd/stella/version.go:11:         0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:137:       0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:177:       0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:246:       0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:24:        0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:287:       0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:337:       0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:380:       0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:407:       0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:51:        0.0%
github.com/CherryHQ/stella/cmd/stella/workflow.go:96:        0.0%
github.com/CherryHQ/stella/cmd/stellad/commands.go:322:      0.0%
github.com/CherryHQ/stella/cmd/stellad/commands.go:338:      0.0%
github.com/CherryHQ/stella/cmd/stellad/commands.go:359:      0.0%
github.com/CherryHQ/stella/cmd/stellad/commands.go:392:      0.0%
github.com/CherryHQ/stella/cmd/stellad/commands.go:470:      0.0%
github.com/CherryHQ/stella/cmd/stellad/commands.go:91:       0.0%
github.com/CherryHQ/stella/cmd/stellad/debug_dump.go:30:     0.0%
github.com/CherryHQ/stella/cmd/stellad/debug_dump.go:35:     0.0%
github.com/CherryHQ/stella/cmd/stellad/debug_dump.go:58:     0.0%
github.com/CherryHQ/stella/cmd/stellad/debug_dump_signal_unix.go:12: 0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:135:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:365:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:376:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:380:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:387:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:395:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:402:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:413:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:421:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:442:       0.0%
github.com/CherryHQ/stella/cmd/stellad/gateway.go:64:        0.0%
github.com/CherryHQ/stella/cmd/stellad/main.go:11:           0.0%
github.com/CherryHQ/stella/cmd/stellad/models.go:12:         0.0%
github.com/CherryHQ/stella/cmd/stellad/models.go:37:         0.0%
github.com/CherryHQ/stella/cmd/stellad/models.go:41:         0.0%
github.com/CherryHQ/stella/cmd/stellad/models.go:50:         0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:107: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:120: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:141: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:164: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:18: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:190: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:195: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:203: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:225: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:26: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:81: 0.0%
github.com/CherryHQ/stella/cmd/stellad/plugin_services.go:94: 0.0%
github.com/CherryHQ/stella/cmd/stellad/postgres.go:118:      0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:101: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:115: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:119: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:123: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:127: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:131: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:142: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:149: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:153: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:161: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:185: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:198: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:215: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:236: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:245: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:272: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:292: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:374: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:393: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:404: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:414: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:422: 0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:56:  0.0%
github.com/CherryHQ/stella/cmd/stellad/service_linux.go:60:  0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_embedding.go:19: 0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_embedding.go:31: 0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_memory.go:20:   0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_memory.go:70:   0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_memory.go:86:   0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_plugins.go:129: 0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_plugins.go:30:  0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_plugins.go:69:  0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_plugins.go:93:  0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_pool.go:23:     0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_pool.go:31:     0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_pool.go:39:     0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_pool.go:84:     0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_reflect.go:27:  0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_reflect.go:45:  0.0%
github.com/CherryHQ/stella/cmd/stellad/setup_skills.go:26:   0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:181:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:221:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:233:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:238:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:338:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:575:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:598:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:608:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:615:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:692:       0.0%
github.com/CherryHQ/stella/cmd/stellad/version.go:768:       0.0%
github.com/CherryHQ/stella/internal/agent/agentctx/context.go:12: 0.0%
github.com/CherryHQ/stella/internal/agent/agentctx/context.go:20: 0.0%
github.com/CherryHQ/stella/internal/agent/agentctx/context.go:29: 0.0%
github.com/CherryHQ/stella/internal/agent/agentctx/context.go:37: 0.0%
github.com/CherryHQ/stella/internal/agent/agentctx/context.go:46: 0.0%
github.com/CherryHQ/stella/internal/agent/agentctx/context.go:66: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:100: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:104: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:108: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:142: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:156: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:165: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:187: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:204: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:214: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:249: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:270: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:305: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:320: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:348: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:371: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:397: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:404: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:414: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:437: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:44: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:461: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:480: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:48: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:501: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:527: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:52: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:539: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:553: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:56: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:580: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:598: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:60: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:615: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:630: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:647: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:68: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:72: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:76: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:80: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:84: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:88: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:92: 0.0%
github.com/CherryHQ/stella/internal/agent/pool_manager.go:96: 0.0%
github.com/CherryHQ/stella/internal/agent/prompt/prompt.go:288: 0.0%
github.com/CherryHQ/stella/internal/agent/runner_impl.go:303: 0.0%
github.com/CherryHQ/stella/internal/agent/runner_impl.go:313: 0.0%
github.com/CherryHQ/stella/internal/agent/runner_impl.go:434: 0.0%
github.com/CherryHQ/stella/internal/agent/runner_impl.go:442: 0.0%
github.com/CherryHQ/stella/internal/agent/runner_impl.go:449: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/chat.go:258: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/chat.go:436: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/chat.go:458: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/options.go:23: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/options.go:31: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/options.go:38: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/options.go:45: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/options.go:55: 0.0%
github.com/CherryHQ/stella/internal/agent/runtime/runner_cache.go:200: 0.0%

vaayne added 2 commits June 26, 2026 20:07
Fixes all 10 findings from the multi-perspective review of the
freeze-goal-into-workflow feature (#594).

- frozen: cap frozen plans at 1024 nodes before instantiation (CR-001)
- server: require agent access + validate project_id on workflow ops (CR-002)
- server: reject type-mismatched contract/policy JSON with 400 (CR-003)
- scheduler: return root_goal_id from dispatchJob and record it on every
  run path, eliminating the back-channel sink (CR-004, CR-010)
- frozen: log orphaned pre-minted sessions on instantiate rollback (CR-005)
- queries: scope Get/Update/Delete workflow by user_id (CR-006)
- workflow: translate goal sentinels at the service boundary (CR-007)
- schema: drop unused system-preset / workflow_key machinery (CR-008)
- cli: report the actual source flag on JSON parse errors (CR-009)

Refs #594
Adds the web surface for the freeze-goal-into-workflow feature so users
can manage frozen workflows without the CLI.

- workflows list page: search + pagination + plan-stats rows (#594)
- workflow detail: frozen plan tree, acceptance contract, edit name/intent
- run (instantiate → jump to the new goal tree), schedule via dialog, delete
- "Save as workflow" action on accepted composite goals
- Workflows tab in the agent facet nav
- en/zh strings for all of the above

All wired to the existing generated client (listWorkflows, getWorkflow,
instantiateWorkflow, scheduleWorkflow, saveGoalAsWorkflow, update/delete).

Refs #594
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.

feat(workflow): freeze a goal into a reusable, schedulable workflow

1 participant