✨ Parameter delivery, substitution, and execution controls - #169
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 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. Comment |
6e80d81 to
f62bfc6
Compare
ibolton336
left a comment
There was a problem hiding this comment.
One inline pointer; not blocking.
| if pod == nil { | ||
| return nil | ||
| } | ||
| for _, cs := range pod.Status.ContainerStatuses { |
There was a problem hiding this comment.
Good catch, and agreed on keeping it out of this PR. Now that #157 has merged (rebased in here), the init-container gap is live: agentContainerTermination only reads containerStatuses, so a skill-loader failure lands as a generic Succeeded=False, reason=Failed at best, and a never-starting pod can miss a terminal signal entirely. Tracked in #171 — I'll widen the pod-read there (init container statuses + a distinct reason) rather than expanding scope here.
6d75bb3 to
e4c20d0
Compare
Replace the hard-coded KONVEYOR_PARAM_MAX_TURNS env read with a generic loader of the controller's three-section /run/konveyor/params.json (ADR 0009, delivered by PR konveyor#169), and append a "## Parameters" section to the agent prompt so workflow/agent values are visible with zero skill involvement. Closes konveyor#117 Assisted-By: Claude Code <noreply@anthropic.com> Signed-off-by: Savitha Raghunathan <saveetha13@gmail.com>
e4c20d0 to
93b6c4d
Compare
Replace the hard-coded KONVEYOR_PARAM_MAX_TURNS env read with a generic loader of the controller's three-section /run/konveyor/params.json (ADR 0009, delivered by PR konveyor#169), and append a "## Parameters" section to the agent prompt so workflow/agent values are visible with zero skill involvement. Closes konveyor#117 Assisted-By: Claude Code <noreply@anthropic.com> Signed-off-by: Savitha Raghunathan <saveetha13@gmail.com>
Replace the hard-coded KONVEYOR_PARAM_MAX_TURNS env read with a generic loader of the controller's three-section /run/konveyor/params.json (ADR 0009, delivered by PR konveyor#169), and append a "## Parameters" section to the agent prompt so workflow/agent values are visible with zero skill involvement. Closes konveyor#117 Assisted-By: Claude Code <noreply@anthropic.com> Signed-off-by: Savitha Raghunathan <saveetha13@gmail.com>
fabianvf
left a comment
There was a problem hiding this comment.
Read through this. Mostly questions.
The harness side is the part I couldn't follow. harness/internal/config/config.go:105 is still the only setter for cfg.MaxTurns and it reads KONVEYOR_PARAM_MAX_TURNS, but I went looking for where params.json gets consumed and came up empty:
$ grep -rn 'params.json\|/run/konveyor' harness/
(nothing)
So I can't work out how execution.maxTurns reaches the harness now, or whether the max_turns param in hack/harness-test/workflow-resources.yaml still does anything. Same for exit 2: os.Exit(1) in harness/cmd/migration-harness/main.go:48 is the only non-zero exit I can find, and the maxTurns path goes through acp/session.go:271 as an error, which I'd expect to land as exit 1. Nothing writes /dev/termination-log either. @djzager is the harness half intentional as-is?
Couple of doc leftovers outside the diff: skills/verify/SKILL.md lines 59 and 84 still tell the model to read KONVEYOR_PARAM_MAX_FIX_ITERATIONS, and harness/README.md:69 still documents KONVEYOR_PARAM_MAX_TURNS. The AgentRun godoc at api/v1alpha1/agentrun_types.go:209 also still says params are injected as env vars, which ships as the CRD description.
Rest is inline.
| if !ok { | ||
| value = p.Default | ||
| } | ||
| if value == "" { |
There was a problem hiding this comment.
value == "" skips the param in strs as well as coerced, so $(agent.x) on a declared param with no supplied value and no default becomes an unresolved reference and fails the run. Makes a referenced param required whatever its required flag says.
There was a problem hiding this comment.
Good catch — that conflated "declared but unset" with "undeclared." Fixed: an unset declared param now resolves to an empty string in $(agent.x) (still omitted from params.json), so only genuinely undeclared references fail. Required params are already caught earlier in validateParams, so anything reaching here is optional.
| if value == "" { | ||
| continue | ||
| } | ||
| strs[p.Name] = value |
There was a problem hiding this comment.
Is the difference between the two scopes here deliberate? strs gets the raw supplied string, but the workflow scope goes through stringifyJSON on the coerced value at line 170, so a boolean supplied as T looks like it renders T in $(agent.flag) and true in $(workflow.flag). params_test.go:78 asserts the agent-side behavior, so I assume one of the two is the one you want.
There was a problem hiding this comment.
Deliberate now, not before: both scopes render the canonical coerced value through one shared stringifyJSON rule, so a value renders identically whichever scope references it — a boolean is true/false regardless of the input spelling (T/yes/1), and numbers normalize the same way. Extended stringifyJSON to cover the agent scope's int64/float64 alongside the workflow scope's json.Number, and updated params_test.go to assert the canonical form (T → true).
| if err := r.validateParams(&run, &agent); err != nil { | ||
| // Validate params against Agent declarations. The resolved params and | ||
| // substitution scopes are reused by createSandbox below. | ||
| params, scopes, err := r.validateParams(&run, &agent) |
There was a problem hiding this comment.
Should this move inside the if run.Status.SandboxName == "" block? Now that validateParams does coercion and substitution it runs on every reconcile of a live run, and findRunsForAgent enqueues non-terminal runs on any Agent write. So if someone edits the Agent's prompt while a run is executing I think it flips to Failed/InvalidParams with the pod still up. Or is re-validating mid-run deliberate?
There was a problem hiding this comment.
Not deliberate — moved pre-creation validation inside the SandboxName == "" branch, so a live run is never re-validated against a since-edited Agent (the pod already captured its rendered prompt/params at creation, so re-validating could only spuriously fail it). Also moved validateGateway for the same reason.
Worth separating: this stops a running stage from breaking, but full immunity of not-yet-created workflow stages to mid-run Agent edits is a bigger determinism question (we snapshot the workflow def in #87, not the Agent defs). Filed #180 to decide snapshot-vs-generation-pin there.
| if err := ctrl.SetControllerReference(run, cm, r.Scheme); err != nil { | ||
| return corev1.Volume{}, corev1.VolumeMount{}, fmt.Errorf("setting ConfigMap owner reference: %w", err) | ||
| } | ||
| if err := r.Create(ctx, cm); err != nil && !errors.IsAlreadyExists(err) { |
There was a problem hiding this comment.
Is the bare Create plus AlreadyExists swallow enough here? createSandbox can still fail after this point, and the content depends on agent.Spec.Params and agent.Spec.Execution rather than just the immutable run spec, so on a retry I think you could get a stale params.json next to a freshly rendered KONVEYOR_PROMPT. createInlineSkillConfigMaps uses CreateOrUpdate and createAgentRunForStage does Get + isOwnedBy, was there a reason to do this one differently?
There was a problem hiding this comment.
No good reason — switched to ctrl.CreateOrUpdate, matching createInlineSkillConfigMaps. On a createSandbox retry the params ConfigMap is now updated to the freshly rendered content instead of an AlreadyExists swallow leaving a stale params.json next to a fresh KONVEYOR_PROMPT.
| }) | ||
| // Capture the harness's opaque termination data (usage/cost | ||
| // report). Stored verbatim, never interpreted (ADR 0018). | ||
| if term := agentContainerTermination(pod); term != nil && json.Valid([]byte(term.Message)) { |
There was a problem hiding this comment.
json.Valid passes arrays and bare scalars but the CRD has terminationData as type: object, so a non-object message would get the whole status patch rejected, setTerminalOutcome included. Unmarshalling into a map[string]any would cover it.
There was a problem hiding this comment.
Fixed — now unmarshals into map[string]any and only stores terminationData when the message is a JSON object, so an array/scalar can't get the whole status patch (and setTerminalOutcome with it) rejected against the type: object schema. Extracted as terminationDataFromPod and covered by TestTerminationDataFromPod.
| var stageParams []konveyoriov1alpha1.ParamValue | ||
| var skipped []string | ||
| for _, p := range pbRun.Spec.Params { | ||
| if declared[p.Name] { |
There was a problem hiding this comment.
declared only has the stage Agent's params, so workflow params that are only used in the guide land in skipped on every stage, even though coerceWorkflowParams uses them fine below.
There was a problem hiding this comment.
Right — a workflow-only param (used in the guide) was being filtered against the stage Agent's declarations and logged as skipped on every stage. Now the filter also consults the snapshotted workflow declarations (pbRun.Status.Params): the stage AgentRun still receives only the params its Agent declares, but a param declared by the workflow (not the stage Agent) is no longer flagged as skipped — only genuinely-undeclared params (typos) are.
| // section) and keep the string form for substituting the guide | ||
| // (workflow scope only — the guide is ambient across stages and must | ||
| // not reference stage-agent params). ADR 0009/0018. | ||
| workflowRaw, workflowStrs, err := coerceWorkflowParams( |
There was a problem hiding this comment.
How should a permanent config error here end up? A bad workflow param value or a $(workflow.typo) in the guide comes back as a plain error and the caller returns it, which I think backs off forever with phase stuck on Running. Spec is immutable and the declarations come from the frozen Status.Params, so I can't see what would ever clear it. The AgentRun side fails terminally with InvalidParams for the same class of thing, should this match?
There was a problem hiding this comment.
It should match, and now does. A permanent workflow config error — a bad workflow param value or an unresolved $(workflow.<name>) in the guide — is wrapped in a configError and the caller fails the run terminally with InvalidParams (phase Failed, completion time set), exactly like the AgentRun side, instead of returning a plain error and backing off forever with phase stuck Running. Genuinely transient errors (Agent lookup, API) still requeue as before.
| // standalone runs. Coercion happens in the workflow controller, | ||
| // which owns the AgentWorkflow param declarations. | ||
| // +optional | ||
| WorkflowParams *runtime.RawExtension `json:"workflowParams,omitempty"` |
There was a problem hiding this comment.
Is workflowParams meant to be user-writable? Agent params get the not declared by Agent check, but this is preserve-unknown-fields on the spec with no webhook, so $(workflow.anything) on a standalone run splices whatever the user wrote into the Agent's prompt. Would a status field, or the ConfigMap the workflow controller already owns, be a better home for it?
There was a problem hiding this comment.
You're right, and digging into it with Tekton as the reference convinced us the root issue is the ambient model itself, not just the missing validation. Tekton passes params by explicit wiring — each pipelineTask maps pipeline params into the Task's own declared params, and the TaskRun carries only the Task's params (no ambient "pipeline params" bag); referenced Tasks must have params passed explicitly, and cross-stage data flows through results. We're doing the opposite: an ambient workflowParams bag on the AgentRun spec plus a $(workflow.*) scope in agent-facing text.
Rather than band-aid it with a webhook, filed #181 to adopt Tekton-style explicit stage wiring: AgentWorkflowStage.params map $(workflow.*) into each stage's declared agent params, the controller stamps the resolved values as the stage AgentRun's own spec.params, and workflowParams / the $(workflow.*) agent scope are removed (the guide keeps $(workflow.*), rendered controller-side). That dissolves this concern at the root — a standalone run then has only agent params, already validated against declarations. It supersedes part of ADR 0009/0018, so it's its own ADR rather than expanding #169; keeping the current mechanism as the dev-preview stopgap here.
| konveyoriov1alpha1 "github.com/konveyor/agentic-controller/api/v1alpha1" | ||
| ) | ||
|
|
||
| // podWithExit builds a pod whose agent container has terminated with the |
There was a problem hiding this comment.
The comment says termination message but the signature is exit code only and Message is hardcoded empty, so the terminationData branch isn't covered anywhere. The workflow tests also still set ConditionTypeReady on the AgentRun at 185, 214, 340 and 529.
There was a problem hiding this comment.
Both addressed. Extracted the termination-message parsing into terminationDataFromPod and added TestTerminationDataFromPod covering the previously-uncovered branch: a JSON object is captured verbatim, while an array/scalar/invalid/empty message yields nothing (the object-only guard from the type: object fix). podWithExit takes a message again so the blob is exercised. And the workflow tests no longer set ConditionTypeReady on the child AgentRun — they now drive stages via the Succeeded condition the sequencer reads.
93b6c4d to
092a662
Compare
|
Thanks for the thorough read. Confirming the split: #169 is intentionally controller-only — it writes On the doc leftovers: the |
Replace the hard-coded KONVEYOR_PARAM_MAX_TURNS env read with a generic loader of the controller's three-section /run/konveyor/params.json (ADR 0009, delivered by PR konveyor#169), and append a "## Parameters" section to the agent prompt so workflow/agent values are visible with zero skill involvement. Closes konveyor#117 Assisted-By: Claude Code <noreply@anthropic.com> Signed-off-by: Savitha Raghunathan <saveetha13@gmail.com>
c43887b to
d1af9f6
Compare
… scope Amends ADR 0011 (execution controls) and ADR 0009 (parameter delivery), surfaced while implementing konveyor#115/konveyor#116/konveyor#119: - Execution fields (mode, maxTurns, maxCost) resolve onto AgentRun.spec; the workflow controller stamps stage-resolved values. Limits are Agent defaults; mode stays an execution-time concern (AgentRun/stage only). - Terminal outcome moves to a Knative/Tekton-style Succeeded condition (Unknown while running, True/False terminal); exit 2 -> LimitReached. Ready is removed from AgentRun; ACPReady (serving) and phase (coarse mirror) are unchanged. - The workflow guide renders from workflow.* only; stage instructions and the agent prompt render from both scopes. Signed-off-by: David Zager <david.j.zager@gmail.com>
Implements ADRs 0009/0011 (with the 0018 amendment) on the controller. Params (konveyor#115/konveyor#116): - Rename AgentParam/AgentRunParam/AgentParamType -> Param/ParamValue/ ParamType (Go only; CRD field names unchanged). - Deliver typed params via /run/konveyor/params.json (workflow/agent/ execution sections) mounted read-only; remove KONVEYOR_PARAM_* env. - $(scope.name) substitution across the agent prompt, run instructions, workflow guide, and stage instructions; unresolved refs and bad coercion fail the run terminally with InvalidParams. - Workflow-level params on AgentWorkflow; resolved workflow params stamped onto stage AgentRuns. Execution controls (konveyor#115): - ExecutionLimits (maxTurns, maxCost) as Agent defaults, overridable per stage; ExecutionSpec (mode + limits) on AgentRun and stages. The Agent cannot declare mode (ADR 0011/0018). Single resolveExecution rule. Terminal model (konveyor#119): - AgentRun gains a Succeeded condition (Unknown while running, True/False terminal) and opaque terminationData read from the pod termination message. Harness exit 0/1/2 -> Succeeded / Failed / LimitReached. Ready is dropped from AgentRun; ACPReady and phase are unchanged. Workflow snapshot (konveyor#87): - AgentWorkflowRun snapshots the full stage definitions, guide, and params at init and executes from the snapshot, so a mid-run workflow edit cannot change stages already planned. Signed-off-by: David Zager <david.j.zager@gmail.com>
- coerceParams: a declared-but-unset param resolves to empty in $(scope.name) instead of failing as an unresolved reference; both scopes stringify the canonical coerced value via one shared rule. - AgentRun: validate params/gateway only before Sandbox creation, so a live run is not re-validated against a since-edited Agent; params ConfigMap uses CreateOrUpdate; terminationData is stored only when the message is a JSON object (CRD type: object). - Workflow sequencer keys off the child AgentRun's Succeeded condition, not phase; shed the legacy Ready condition on live AgentRuns. - Workflow: workflow-declared params are no longer flagged as skipped; permanent workflow config errors fail the run terminally (InvalidParams) instead of requeuing forever. - ParamsFilePath is the single source of truth for the mount path/key. - Docs: AgentRun godoc no longer says params are injected as env vars. - Tests: canonical stringify, unset-param, terminationData object-only, and workflow stages driven via Succeeded. Signed-off-by: David Zager <david.j.zager@gmail.com>
konveyor#143 (merged) surfaces the harness's human-readable termination message (e.g. a non-git source) on the terminal condition. Our Succeeded model replaced the Ready condition, so thread that message through setTerminalOutcome: it is preferred over the generic reason on a failure outcome and now lands on Succeeded=False. terminationData still captures a JSON-object usage blob; a plain-string failure message is surfaced as the condition message. Updated the konveyor#143 test to assert Succeeded. Signed-off-by: David Zager <david.j.zager@gmail.com>
d1af9f6 to
c469132
Compare
…174) Replace the hard-coded KONVEYOR_PARAM_MAX_TURNS env read with a generic loader of the controller's three-section /run/konveyor/params.json (ADR 0009, delivered by PR #169), and append a "## Parameters" section to the agent prompt so workflow/agent values are visible with zero skill involvement. Closes #117 Assisted-By: Claude Code <noreply@anthropic.com> <!-- ## PR Title Prefix Every **PR Title** should be prefixed with an emoji alias to indicate its type. - Breaking change:⚠️ (`⚠️ `) - Non-breaking feature: ✨ (`✨`) - Patch fix: 🐛 (`🐛`) - Docs: 📖 (`📖`) - Infra/Tests/Other: 🌱 (`🌱`) - No release note: 👻 (`👻`) For example, a pull request containing a new feature might look like `✨ Add agent status reporting`. Use the **alias** (`✨`) not the emoji character directly. ## Changelog Fragment PRs with `✨`, `🐛`, or `⚠️ ` prefixes require a changelog fragment in `changes/unreleased/`. Create one with: make changelog-create NAME=<pr-number>-<short-description> KIND=<kind> Or copy `changes/template.yaml` to `changes/unreleased/<name>.yaml` and edit it. For more information, see the Konveyor [Versioning Doc](https://github.com/konveyor/release-tools/blob/main/VERSIONING.md). --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Harness parameters and execution controls are now loaded from a controller-provided JSON file. * Workflow and agent settings appear in a dedicated “Parameters” section of the agent prompt. * Added maximum-turn and cost-limit enforcement with graceful handoff behavior. * Harness exits distinguish successful, failed, and limit-reached outcomes. * Usage summaries are recorded for every execution. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Savitha Raghunathan <saveetha13@gmail.com>
## What Update `github.com/konveyor/agentic-controller/api` from the pre-parameter-delivery commit to the merge commit of konveyor/agentic-controller#169. This lets the Hub agentic endpoints deserialize and forward the structured parameter and execution-control fields introduced by that PR, including workflow parameter declarations and `AgentRun.spec.execution`. ## Validation - `go test github.com/konveyor/agentic-controller/api/v1alpha1` - Full Hub package compilation is blocked in the lightweight dev container because CGO/sqlite3 is unavailable; no Hub source behavior changes are included. ## Follow-up A companion tackle2-ui PR consumes the updated contract in the run-creation forms. Signed-off-by: Ian Bolton <ibolton336@users.noreply.github.com> Signed-off-by: Ian Bolton <ibolton@redhat.com>
## What Integrate the parameter delivery and execution-control contract from konveyor/agentic-controller#169 into the agentic console. - align the handwritten TypeScript contract with the controller API - serialize standalone run supervision mode under `spec.execution.mode` - render typed standalone Agent params - show workflow-scoped and stage-Agent params in separate form sections - allow stage-specific params instead of requiring every stage Agent to declare them - add request serialization coverage The browser still does not create `params.json`; it supplies run values through Hub, and the controller validates/coerces them and writes `/run/konveyor/params.json`. ## Dependency - konveyor/tackle2-hub#1129 ## Validation - `npm run lint` (passes with the repository's 20 existing warnings) - `npm run build` - `npm test -w client -- --runInBand src/app/api/rest/agent-runs.test.ts` (2 tests pass) ## Contract note Issue #3524 mentions `allowedModes`, but the reconciled #169/ADR 0018 contract has invocation-level `auto | approve` mode and Agent/stage-owned budget limits. This change follows the merged controller API. Closes #3524 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added automatic and approval-required supervision modes when creating runs, including approval guidance. * Added structured validation for agent and workflow run parameters. * Workflow run forms now distinguish workflow-level parameters from stage-agent parameters. * Added support for execution limits, Git configuration, and parameter metadata. * **Documentation** * Added guidance for defining, validating, delivering, and troubleshooting agentic run parameters and supervision modes. * **Tests** * Added coverage for run creation, parameter serialization, validation, and approval-mode behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Ian Bolton <ibolton@redhat.com>
… says why (#232) Closes #231. ## Problem goose renders a rejected model call as assistant prose and ends the turn normally: ``` Ran into this error: Server error: Failed to call Bedrock: … UnrecognizedClientException … "The security token included in the request is invalid." … ``` The harness recognised that text since #170 but only logged a WARN and told viewers to *"see the pod log"*. Exit 0, `stopReason=end_turn`, run **Succeeded** in green, Gateway **Ready** (Bedrock cannot be probed, #222). On the demo cluster today three runs died this way on a rotated AWS key and were read as a broken plan ladder, because nothing anyone could see said otherwise. ## Change **Harness** - `classifyOutcome` takes `providerError` (the existing `PromptResult.ClosingProviderError()`). With `TurnsUsed == 0` the stage is `outcomeFailed`: the model never worked, so nothing succeeded. A provider error *after* real work keeps the turn's outcome — that is #129's broader contract — but still gets the notice below. - The termination log's `stopReason` becomes `provider error: <the provider's error line>` (redacted, cut at 1000 runes). The line is the failure itself: `acp.ProviderErrorText` skips narration goose streamed ahead of it (*"Here is the plan:"* … *"Ran into this error: …"*), and `LooksLikeProviderError` is built on it, so detection and selection cannot drift. The cap is sized for #231's real line: goose renders the AWS SDK error with Debug formatting, ~500 runes with *"The security token included in the request is invalid."* past rune 160, which a 200-rune cut lost. - The viewer notice quotes the same line — *"the model provider rejected the call — no work was done: Ran into this error: … security token … is invalid."* — instead of pointing at a pod log a console user cannot open. The stage-end notice and the returned error name it too. **Controller** Unchanged. The termination blob stays opaque to the controller (ADR 0011, ADR 0018, `CONTEXT.md`): it is stored verbatim on `status.terminationData`, and `setTerminalOutcome` copies the termination message onto the `Succeeded=False` condition as before. Showing `terminationData.stopReason` beside the phase instead of the raw blob belongs in the UI, which knows the harness schema — a tackle2-ui follow-up. Net effect: the run is **Failed** instead of Succeeded, and the provider's error line — the sentence that tells the operator to rotate the Gateway credential — is in the viewer notice and in the termination blob's `stopReason`, which the console shows raw beside the phase until the UI renders it. Exit-code contract (ADR 0011) is unchanged: this is an exit-1 failure, not a new code. #170's *"exit status is unchanged on purpose … belongs with the Succeeded condition work in #119"* deferral is what this resolves; #119 landed in #169. ## Verification - `gofmt`, `go vet`, `go test ./...` green in `harness/`; `go vet` plus `TestSetTerminalOutcome`, `TestPodTerminationMessage`, `TestTerminationDataFromPod` green in `internal/controller/`. - New cases: `TestClassifyOutcome` — provider error with no turns → failed; after 7 turns → succeeded; at the native limit → limitReached still wins. `TestProviderErrorText` — the failure without the narration ahead of it, in both of goose's forms; a quoted phrase is not a failure. `TestProviderErrorSummary` — narration in the same or an earlier message, the trailer form, #231's Bedrock line kept whole, the 1000-rune cut. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: ibolton336 <ibolton@redhat.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Implements ADRs 0009/0011 on the controller, with amendments captured in a new ADR 0018.
Closes #115
Closes #116
Closes #119
Closes #87
What's here
Params (#115/#116)
AgentParam/AgentRunParam/AgentParamType→Param/ParamValue/ParamType(Go only; CRD field names unchanged)./run/konveyor/params.json(workflow/agent/execution sections), mounted read-only; remove the oldKONVEYOR_PARAM_*env vars.$(scope.name)substitution across the agent prompt, run instructions, workflow guide, and stage instructions. Unresolved refs and bad type coercion fail the run terminally withInvalidParams.AgentWorkflow; resolved workflow params stamped onto stage AgentRuns.Execution controls (#115)
ExecutionLimits(maxTurns,maxCost) are Agent defaults, overridable per stage;ExecutionSpec(mode+ limits) lives on AgentRun and stages. The Agent cannot declaremode(execution-time concern — ADR 0011/0018). SingleresolveExecutionrule.Terminal model (#119)
Succeededcondition (Unknownwhile running,True/Falseterminal) and opaqueterminationDataread from the pod termination message.0/1/2→Succeeded/Failed/LimitReached.Readyis removed from AgentRun;ACPReady(serving) andphase(coarse mirror) are unchanged.Workflow snapshot (#87)
ADR 0018
Amends ADR 0011 (execution controls) and ADR 0009 (parameter delivery) — three places where the merged text didn't survive contact with the controller: stage limits had nowhere to land, exit-2 →
Succeededcontradicted workflow sequencing, and a workflow guide has no single agent scope. Builds on the pod-read plumbing merged in #160.Test plan
make test— envtest suite green (unit coverage 78.4%), including terminal-outcome exit-code mapping and the three-section params.json.make lint— 0 issues.make generate manifests— CRDs regenerated and committed.