requirements cannot be expressed; agent picking ignores rule hostnames #90

Open
opened 2026-10-08 09:21:21 +02:00 by EduarD3V · 6 comments
Owner

Labels: bug, scheduling, priority/high

The plans store has a requirements field (os, commands, architecture, capabilities) with capabilities: ["workflow_v2"]. create_plan exposes no parameter for it. The agent cannot declare what a plan needs.

Consequence, observed: execution 643895b6 ran plan 0c52ac87 — a script hardcoded to adam-slave-0 paths (/etc/rsnapshot.conf, /home/eduardez/backups) — on adam-slave-1, where it found nothing, printed PLAN ABORTED, and was recorded remediated. Rule 3749b312 listed hostnames: ["adam-slave-0","adam-master"], so the hostname filter was not honoured on that dispatch path. Either the filter is not enforced, or a manual dispatch bypasses it silently.

Either way the execution record does not distinguish rule-triggered from manually-triggered runs.

Fix:

create_plan accepts requirements.
Dispatch honours the rule's hostnames / groupIds; a manual run that violates them is refused or flagged.
Execution records carry trigger: rule | manual and the originating ruleId (present for rule runs, absent for 643895b6 — the ambiguity is invisible in the store).

Acceptance criteria

requirements settable at create.
hostnames/groupIds on a rule are enforced at dispatch.
Manual dispatch outside the filter is refused with a clear reason, or marked.
Every execution records trigger and, for rule runs, ruleId.
Labels: bug, scheduling, priority/high The plans store has a requirements field (os, commands, architecture, capabilities) with capabilities: ["workflow_v2"]. create_plan exposes no parameter for it. The agent cannot declare what a plan needs. Consequence, observed: execution 643895b6 ran plan 0c52ac87 — a script hardcoded to adam-slave-0 paths (/etc/rsnapshot.conf, /home/eduardez/backups) — on adam-slave-1, where it found nothing, printed PLAN ABORTED, and was recorded remediated. Rule 3749b312 listed hostnames: ["adam-slave-0","adam-master"], so the hostname filter was not honoured on that dispatch path. Either the filter is not enforced, or a manual dispatch bypasses it silently. Either way the execution record does not distinguish rule-triggered from manually-triggered runs. Fix: create_plan accepts requirements. Dispatch honours the rule's hostnames / groupIds; a manual run that violates them is refused or flagged. Execution records carry trigger: rule | manual and the originating ruleId (present for rule runs, absent for 643895b6 — the ambiguity is invisible in the store). Acceptance criteria requirements settable at create. hostnames/groupIds on a rule are enforced at dispatch. Manual dispatch outside the filter is refused with a clear reason, or marked. Every execution records trigger and, for rule runs, ruleId.
gotoi-agent added reference issue/90-requirements-hostnames 2026-10-09 10:31:29 +02:00
Collaborator

Claimed — branch issue/90-requirements-hostnames. Doing: expose requirements on create_plan/plan_update tools + hostAllowed gate on manual run + Execution.trigger.

Claimed — branch `issue/90-requirements-hostnames`. Doing: expose requirements on create_plan/plan_update tools + hostAllowed gate on manual run + Execution.trigger.
Collaborator

Issue #90 plan — create_plan requirements + dispatch honours rule hostnames

Branch: issue/90-requirements-hostnames (base 5b84430). Worktree: /home/eduardez/workspace/gotoi-wt/issue-90.

Restated goal

Three gaps, all verified on base 5b84430:

  1. The create_plan / plan_update control tools expose no requirements parameter, so the AI
    cannot declare what a plan needs (os/architecture/capabilities/commands).
  2. POST /plans/:id/run (manual dispatch) never consults the plan's referencing rules' hostnames /
    groupIds, so a rule-scoped plan can be hand-run on any host.
  3. Execution has ruleId? but no trigger, so the record cannot distinguish a rule-triggered run
    from a manual or retried one.

Deliver: tool params + end-to-end passthrough for requirements; a hostAllowed guard on manual
run; Execution.trigger?: 'rule' | 'manual' | 'retry' persisted, projected, and mirrored to the
dashboard types; UCs UC-MW-176 / UC-MW-177; two new red-first tests.

Project fact that contradicts the task file

Task row "API create/patch accept requirements" (MET) is false on base 5b84430.
planCreateBody (middleware/src/routes/plans.ts:56-82) and planPatchBody (L84-108) carry no
requirements key; createPlanFromBody's stampPlan({...}) (L174-192) never copies it;
applyPlanPatch hard-preserves requirements: prev.requirements (middleware/src/plans.ts:262) and
has no hasOwnProperty branch for it. Plan.requirements (type + jsonb column + compatibility())
does exist and is read by scoring/templates — only the write path is missing.

Consequence: a params-only change in control-agent.ts would still drop the value, because the tool
run fns funnel through planCreateBody / applyPlanPatch. The frozen design's "params + passthrough
change only" therefore expands to include the REST create/patch field, otherwise gap 1 is not fixed.
This is the smallest correct diff for the stated goal; recorded here rather than silently widening scope.

Scope

In:

  • middleware/src/routes/plans.ts — requirements in planCreateBody/planPatchBody/
    createPlanFromBody; hostAllowed gate + trigger: 'manual' in POST /plans/:id/run.
  • middleware/src/plans.ts — PlanDraft.requirements, planDraftSchema, applyPlanPatch branch.
  • middleware/src/control-agent.ts — create_plan / plan_update params, PLAN_PATCH_KEYS,
    planPatchToolSchema, descriptions.
  • middleware/src/pipeline.ts, middleware/src/incidents.ts — trigger: 'rule' where a matched
    rule created the execution.
  • middleware/src/routes/executions.ts — trigger: 'retry'.
  • middleware/src/types.ts, middleware/src/db/schema.ts,
    middleware/drizzle/0034_execution_trigger.sql + .down.sql,
    middleware/src/db/persist.ts, middleware/src/views.ts,
    dashboard/src/lib/types.ts.
  • New UCs + two new tests.

Out (per task): agent/**; middleware/src/match.ts; middleware/src/rules.ts semantics;
capabilities.ts; plans/; typed steps (#89).

UC decision

  • UC-MW-176-requirements-tool.md — "create_plan and plan_update carry requirements" (control tool
    accepts + persists requirements; related: UC-MW-68, UC-MW-136, UC-MW-137).
  • UC-MW-177-dispatch-trigger-record.md — "manual dispatch honours rule host scoping and executions
    record their trigger" (related: UC-MW-101, UC-MW-108).
  • Bidirectional: edit partner UCs' related: + ## Related heading too.

Ordered tasks

Each task ends with its single verifying command; commit after each green step (Refs #90).

  1. UCs + partner related: edits → python3 docs/use-cases/_check.py
  2. Red-first tests middleware/tests/uc-mw-176-requirements-tool.test.ts,
    uc-mw-177-dispatch-trigger-record.test.ts → cd middleware && bun test tests/uc-mw-176-requirements-tool.test.ts tests/uc-mw-177-dispatch-trigger-record.test.ts (expected red)
  3. requirements end to end (plans.ts draft/patch/schema + routes/plans.ts bodies + control-agent
    tool params) → cd middleware && bun test tests/uc-mw-176-requirements-tool.test.ts tests/uc-mw-136-ai-read-table.test.ts tests/uc-mw-137-ai-create-plan-rule.test.ts
  4. hostAllowed gate + trigger field plumbing (types, schema, migration, persist, views,
    dashboard mirror, stamping sites) → cd middleware && bun test tests/uc-mw-177-dispatch-trigger-record.test.ts tests/uc-mw-100-rules.test.ts tests/uc-mw-05-match-plan.test.ts
  5. All Step 3 gates.

Open questions and assumptions

  • Q: does the REST API already accept requirements? No (verified above). Assumption: fixing it
    is in scope because the tool run fns delegate to planCreateBody/applyPlanPatch.
  • Q: which executions get trigger: 'rule'? Those created from a matched rule: pipeline.ts:273-287
    and incidents.ts:202-218 (both stamp ruleId: m.ruleId). retry → routes/executions.ts:114-128;
    manual → routes/plans.ts:1096-1109. Other creation sites (agent-step.ts, control-agent.ts,
    executions.ts rollback/chain, routes/agents.ts, pipeline.ts:114 diagnostic) have no matched
    rule; assumption: leave trigger undefined there — optional field, no behaviour change.
  • Q: 409 or flag on a host-scoped manual run? Assumption: refuse with 409 naming the rule id and
    its allowed hostnames/groups, mirroring the existing status:draft 409 style. No referencing rule =
    no restriction (seed disk-cleanup rule has none, so existing tests stay green).
  • Q: requirements on plan_update for a template/shared plan? Shared plans are not editable
    (403), unchanged.

Risk + boring fallback

  • Risk: adding a non-optional field to executionSummary's Omit<Execution, ...> return type forces
    a TS edit there; handled in task 4.
  • Risk: capability mismatch of a new requirement vs a chosen agent. decide() already gates
    compatibility; the tool change only stores the value. If an end-to-end "incompatible agent refused"
    assertion turns out to depend on capabilities the test agent cannot report, the boring path is to
    assert the stored plan.requirements and that compatibility() rejects the target agent, and note
    it — no new gate logic (two-attempt cap).
  • Risk: migration numbering. Next free is 0034 (0033_plan_typed_steps.sql is latest). Fallback
    if it collides: use the next free integer following the same ALTER TABLE ... ADD COLUMN IF NOT EXISTS / DROP COLUMN IF EXISTS pattern.
  • Boring fallback overall: if the hostAllowed gate cannot be made to work without touching
    match.ts/rules.ts, stop, keep the diff to requirements + trigger, and report PARTIAL with
    the reason.
# Issue #90 plan — create_plan requirements + dispatch honours rule hostnames Branch: `issue/90-requirements-hostnames` (base `5b84430`). Worktree: `/home/eduardez/workspace/gotoi-wt/issue-90`. ## Restated goal Three gaps, all verified on base `5b84430`: 1. The `create_plan` / `plan_update` control tools expose no `requirements` parameter, so the AI cannot declare what a plan needs (`os`/`architecture`/`capabilities`/`commands`). 2. `POST /plans/:id/run` (manual dispatch) never consults the plan's referencing rules' `hostnames` / `groupIds`, so a rule-scoped plan can be hand-run on any host. 3. `Execution` has `ruleId?` but no `trigger`, so the record cannot distinguish a rule-triggered run from a manual or retried one. Deliver: tool params + end-to-end passthrough for `requirements`; a `hostAllowed` guard on manual run; `Execution.trigger?: 'rule' | 'manual' | 'retry'` persisted, projected, and mirrored to the dashboard types; UCs UC-MW-176 / UC-MW-177; two new red-first tests. ## Project fact that contradicts the task file Task row "API create/patch accept `requirements`" (**MET**) is **false** on base `5b84430`. `planCreateBody` (`middleware/src/routes/plans.ts:56-82`) and `planPatchBody` (L84-108) carry no `requirements` key; `createPlanFromBody`'s `stampPlan({...})` (L174-192) never copies it; `applyPlanPatch` hard-preserves `requirements: prev.requirements` (`middleware/src/plans.ts:262`) and has no `hasOwnProperty` branch for it. `Plan.requirements` (type + jsonb column + `compatibility()`) does exist and is read by scoring/templates — only the write path is missing. Consequence: a params-only change in `control-agent.ts` would still drop the value, because the tool run fns funnel through `planCreateBody` / `applyPlanPatch`. The frozen design's "params + passthrough change only" therefore expands to include the REST create/patch field, otherwise gap 1 is not fixed. This is the smallest correct diff for the stated goal; recorded here rather than silently widening scope. ## Scope In: - `middleware/src/routes/plans.ts` — `requirements` in `planCreateBody`/`planPatchBody`/ `createPlanFromBody`; `hostAllowed` gate + `trigger: 'manual'` in `POST /plans/:id/run`. - `middleware/src/plans.ts` — `PlanDraft.requirements`, `planDraftSchema`, `applyPlanPatch` branch. - `middleware/src/control-agent.ts` — `create_plan` / `plan_update` params, `PLAN_PATCH_KEYS`, `planPatchToolSchema`, descriptions. - `middleware/src/pipeline.ts`, `middleware/src/incidents.ts` — `trigger: 'rule'` where a matched rule created the execution. - `middleware/src/routes/executions.ts` — `trigger: 'retry'`. - `middleware/src/types.ts`, `middleware/src/db/schema.ts`, `middleware/drizzle/0034_execution_trigger.sql` + `.down.sql`, `middleware/src/db/persist.ts`, `middleware/src/views.ts`, `dashboard/src/lib/types.ts`. - New UCs + two new tests. Out (per task): `agent/**`; `middleware/src/match.ts`; `middleware/src/rules.ts` semantics; `capabilities.ts`; `plans/`; typed steps (#89). ## UC decision - `UC-MW-176-requirements-tool.md` — "create_plan and plan_update carry requirements" (control tool accepts + persists `requirements`; `related:` UC-MW-68, UC-MW-136, UC-MW-137). - `UC-MW-177-dispatch-trigger-record.md` — "manual dispatch honours rule host scoping and executions record their trigger" (related: UC-MW-101, UC-MW-108). - Bidirectional: edit partner UCs' `related:` + `## Related` heading too. ## Ordered tasks Each task ends with its single verifying command; commit after each green step (`Refs #90`). 1. UCs + partner `related:` edits → `python3 docs/use-cases/_check.py` 2. Red-first tests `middleware/tests/uc-mw-176-requirements-tool.test.ts`, `uc-mw-177-dispatch-trigger-record.test.ts` → `cd middleware && bun test tests/uc-mw-176-requirements-tool.test.ts tests/uc-mw-177-dispatch-trigger-record.test.ts` (expected red) 3. `requirements` end to end (plans.ts draft/patch/schema + routes/plans.ts bodies + control-agent tool params) → `cd middleware && bun test tests/uc-mw-176-requirements-tool.test.ts tests/uc-mw-136-ai-read-table.test.ts tests/uc-mw-137-ai-create-plan-rule.test.ts` 4. `hostAllowed` gate + `trigger` field plumbing (types, schema, migration, persist, views, dashboard mirror, stamping sites) → `cd middleware && bun test tests/uc-mw-177-dispatch-trigger-record.test.ts tests/uc-mw-100-rules.test.ts tests/uc-mw-05-match-plan.test.ts` 5. All Step 3 gates. ## Open questions and assumptions - **Q: does the REST API already accept `requirements`?** No (verified above). *Assumption: fixing it is in scope because the tool run fns delegate to `planCreateBody`/`applyPlanPatch`.* - **Q: which executions get `trigger: 'rule'`?** Those created from a matched rule: `pipeline.ts:273-287` and `incidents.ts:202-218` (both stamp `ruleId: m.ruleId`). `retry` → `routes/executions.ts:114-128`; `manual` → `routes/plans.ts:1096-1109`. Other creation sites (`agent-step.ts`, `control-agent.ts`, `executions.ts` rollback/chain, `routes/agents.ts`, `pipeline.ts:114` diagnostic) have no matched rule; *assumption: leave `trigger` undefined there — optional field, no behaviour change.* - **Q: 409 or flag on a host-scoped manual run?** *Assumption: refuse with 409 naming the rule id and its allowed hostnames/groups, mirroring the existing `status:draft` 409 style. No referencing rule = no restriction (seed disk-cleanup rule has none, so existing tests stay green).* - **Q: `requirements` on `plan_update` for a template/shared plan?** Shared plans are not editable (403), unchanged. ## Risk + boring fallback - Risk: adding a non-optional field to `executionSummary`'s `Omit<Execution, ...>` return type forces a TS edit there; handled in task 4. - Risk: capability mismatch of a new requirement vs a chosen agent. `decide()` already gates compatibility; the tool change only stores the value. If an end-to-end "incompatible agent refused" assertion turns out to depend on capabilities the test agent cannot report, the boring path is to assert the stored `plan.requirements` and that `compatibility()` rejects the target agent, and note it — no new gate logic (two-attempt cap). - Risk: migration numbering. Next free is **0034** (`0033_plan_typed_steps.sql` is latest). Fallback if it collides: use the next free integer following the same `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` / `DROP COLUMN IF EXISTS` pattern. - Boring fallback overall: if the `hostAllowed` gate cannot be made to work without touching `match.ts`/`rules.ts`, stop, keep the diff to `requirements` + `trigger`, and report PARTIAL with the reason.
Collaborator

REVIEW: FIXED 2 — findings: (1) five partner UCs got doubled '- -' Related bullets from the related: edit — fixed c4de134; (2) issue requirement 'every execution records trigger' was missing on operator agent-test, control-chat machine-command, rollback, and chain runs (chain kept ruleId but dropped trigger) — fixed 7d11488, alert diagnostics with no matched rule stay undefined and are documented in UC-MW-177 4a; static gates all green (UC check, AI headers, type mirror, no Showing lines in source). UNFIXED: implementer report file issue-90.report.md absent (anomaly, cannot retro-create); pre-existing flake tests/uc-mw-114-agents-bulk.test.ts 5s timeout reproduced on clean origin/agent-develop — not from this diff.

REVIEW: FIXED 2 — findings: (1) five partner UCs got doubled '- -' Related bullets from the related: edit — fixed c4de134; (2) issue requirement 'every execution records trigger' was missing on operator agent-test, control-chat machine-command, rollback, and chain runs (chain kept ruleId but dropped trigger) — fixed 7d11488, alert diagnostics with no matched rule stay undefined and are documented in UC-MW-177 4a; static gates all green (UC check, AI headers, type mirror, no Showing lines in source). UNFIXED: implementer report file issue-90.report.md absent (anomaly, cannot retro-create); pre-existing flake tests/uc-mw-114-agents-bulk.test.ts 5s timeout reproduced on clean origin/agent-develop — not from this diff.
Collaborator

Implementation landed — worktree pushed

PR opened: #102

5 commits, 24 files, +569/-36 lines. Two new UCs (MW-176 requirements tool,
MW-177 dispatch trigger). All static gates green; the 1-fail middleware result
is the pre-existing UC-MW-19 baseline red (also fails on clean).

Refs #90

## Implementation landed — worktree pushed PR opened: https://git.eduard3v.com/EduarD3V/gotoi/pulls/102 5 commits, 24 files, +569/-36 lines. Two new UCs (MW-176 requirements tool, MW-177 dispatch trigger). All static gates green; the 1-fail middleware result is the pre-existing UC-MW-19 baseline red (also fails on clean). Refs #90
gotoi-agent changed reference from issue/90-requirements-hostnames to issue/90-requirements-hostnames 2026-10-09 13:04:25 +02:00
Collaborator

DONE (runner, 2026-10-09 12:5x) — PR: #103 (draft) against agent-develop.

Branch issue/90-requirements-hostnames, 5 commits (3 implementer 1f140de/5a9cda9/17d0e39 + 2 reviewer c4de134/7d11488). The implementer session was killed by the harness after its own gate run (ALL_GATES_DONE 11:38, before DONE); the runner harvested committed work, ran §4.6 review + §5 gates, and published.

review: FIXED 2 — (1) trigger stamping was missing on agent-test / machine-command / rollback / chain runs (7d11488); (2) doubled Related bullets in partner UCs (c4de134). Reviewer report: meta/issue-90.review.md (run archive).

Gates on final tree: static all green (UC check, headers 581, type-mirror, Showing-lines, gofmt); dashboard 254/254 + svelte-check 0 err; middleware 634/635 (only documented pre-existing UC-MW-19); go agent 6 fails all reproduced identically on clean origin/agent-develop (environmental /etc/gotoi/agent.json, no agent/ files in diff).

DRAFT reason — hidden UC-OP-02 set: times out at 5000 ms on branch (5083–5101 ms, 3 runs, 2 isolated); clean origin/agent-develop baseline passes 5055 ms (55 ms margin). Branch adds migration 0034 to every buildApp boot (~30–45 ms PGlite), pushing the frozen hidden set's budget over the line. Counters code untouched; visible UC-OP-02 twin passes. OPERATOR CALL: raise hidden-set budget (e.g. 8000 ms) or slim hidden boot — this will fire on every future migration otherwise.

Refs #90

DONE (runner, 2026-10-09 12:5x) — PR: https://git.eduard3v.com/EduarD3V/gotoi/pulls/103 (draft) against agent-develop. Branch issue/90-requirements-hostnames, 5 commits (3 implementer 1f140de/5a9cda9/17d0e39 + 2 reviewer c4de134/7d11488). The implementer session was killed by the harness after its own gate run (ALL_GATES_DONE 11:38, before DONE); the runner harvested committed work, ran §4.6 review + §5 gates, and published. review: FIXED 2 — (1) trigger stamping was missing on agent-test / machine-command / rollback / chain runs (7d11488); (2) doubled Related bullets in partner UCs (c4de134). Reviewer report: meta/issue-90.review.md (run archive). Gates on final tree: static all green (UC check, headers 581, type-mirror, Showing-lines, gofmt); dashboard 254/254 + svelte-check 0 err; middleware 634/635 (only documented pre-existing UC-MW-19); go agent 6 fails all reproduced identically on clean origin/agent-develop (environmental /etc/gotoi/agent.json, no agent/ files in diff). DRAFT reason — hidden UC-OP-02 set: times out at 5000 ms on branch (5083–5101 ms, 3 runs, 2 isolated); clean origin/agent-develop baseline passes 5055 ms (55 ms margin). Branch adds migration 0034 to every buildApp boot (~30–45 ms PGlite), pushing the frozen hidden set's budget over the line. Counters code untouched; visible UC-OP-02 twin passes. OPERATOR CALL: raise hidden-set budget (e.g. 8000 ms) or slim hidden boot — this will fire on every future migration otherwise. Refs #90
Collaborator

CORRECTION (runner): PR #102 — opened by the original worker session after its gates and reviewed-fixed at c4de134/7d11488 — was merged into agent-develop by the integrator (merge commit 3122e89) before this runner published. Runner's duplicate draft #103 closed with explanation; #102 is the record. Hidden UC-OP-02 5000 ms budget finding still stands as an operator item: it will time out on every future PGlite migration (baseline 5055 ms, +1 migration ≈ 5083–5101 ms).

CORRECTION (runner): PR #102 — opened by the original worker session after its gates and reviewed-fixed at c4de134/7d11488 — was merged into agent-develop by the integrator (merge commit 3122e89) before this runner published. Runner's duplicate draft #103 closed with explanation; #102 is the record. Hidden UC-OP-02 5000 ms budget finding still stands as an operator item: it will time out on every future PGlite migration (baseline 5055 ms, +1 migration ≈ 5083–5101 ms).
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
EduarD3V/gotoi#90
No description provided.