Skip to content

feat: persist automation scheduler state#998

Merged
Astro-Han merged 22 commits into
devfrom
codex/i950-automation-pr4-persistence
May 30, 2026
Merged

feat: persist automation scheduler state#998
Astro-Han merged 22 commits into
devfrom
codex/i950-automation-pr4-persistence

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

Adds issue #950 PR4 backend persistence and durable scheduling behavior for automations:

  • persists automation definitions and run ledgers in SQLite
  • reloads definitions/runs across instance restarts and reconciles stale active runs
  • adds cron next-fire computation for the frozen 5-field cron contract
  • adds durable scheduler ownership with a no-timer fallback when another process owns the lock
  • adds a durable active-run guard so runNow cannot bypass same-project writer protection across processes

Why

PR1 froze the automation contract, PR2 made manual runNow execute, and PR3 added in-memory scheduler timers. PR4 makes those records durable and prevents duplicated background firing when multiple app/server processes are open.

This PR follows the merged PR1-3 contract: terminal non-attempts use state: "stopped" plus stopReason such as missed_schedule, expired, or blocker_lost. It does not reopen the route/schema surface back to separate skipped or expired states.

Related Issue

Closes part of #950. This is PR4 in the agreed 7-PR breakdown.

Human Review Status

Pending

Review Focus

Please focus on the persisted automation table shape, owner-directory scoping, durable active-run guard, scheduler owner fallback/retry behavior, cron next-fire semantics, and stale run reconciliation mapping.

Risk Notes

Migration/data behavior: this adds new SQLite tables for automation definitions and runs. Definitions and run ledgers are scoped by project plus owner directory.

Concurrency behavior: background timers only run in the process that owns the durable scheduler lock. Non-owners can still serve CRUD and runNow, but execution is guarded by durable active-run state.

Cron scope: cron supports the existing frozen 5-field validation subset. This PR computes next fires by minute iteration with the definition timezone; it does not add a broader cron parser or PR5 worktree support.

Skipped checklist items:

  • Visible UI check: no visible UI or copy changed.
  • Platform/packaging check: no platform, packaging, updater, signing, shell, or native path behavior changed.

How To Verify

Diff whitespace check: git diff --check origin/dev...HEAD -> passed
Automation regression bundle: OPENCODE_DB=:memory: bun test test/server/automation-runner.test.ts test/server/automation-scheduler.test.ts test/server/automation-routes.test.ts test/server/automation-event-fixtures.test.ts test/server/event-replay.test.ts test/tool/automate.test.ts --timeout 30000 -> 109 passed, 0 failed
Typecheck: OPENCODE_DB=:memory: bun --cwd packages/opencode typecheck -> passed

Screenshots or Recordings

Not applicable. No visible UI changes.

Checklist

How to use this checklist:

  • Tick a box by replacing [ ] with [x]. Do not edit, add, or remove items.
  • The bot-applied label items can only be honestly ticked AFTER the PR is opened and the labeler / priority-triage bots have run — return to the PR description and tick them then.
  • Most items are required. The few that are conditional are explicitly marked (conditional); for those, leave unticked if they truly do not apply and explain why in Risk Notes. All other items must be ticked before requesting human review.
  • Type label — this PR carries exactly one of bug, enhancement, task, documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

  • New Features

    • Automations now persist to the database for durability across restarts
    • Added cron-based recurring schedules with timezone-aware firing
    • Distributed ownership/lease coordination to avoid duplicate runs across instances
  • Refactor

    • State moved from in-memory caches to durable storage with transactional run/definition handling
    • Scheduler logic updated to honor ownership and timezone in schedule identity
  • Tests

    • Expanded tests for durability, reconciliation, cron semantics, and multi-instance coordination

Review Change Stack

@Astro-Han Astro-Han added enhancement New feature or request P1 High priority harness Model harness, prompts, tool descriptions, and session mechanics labels May 30, 2026
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@Astro-Han, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 19 minutes and 25 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a752fed-495d-40e0-a915-80c3c795ce45

📥 Commits

Reviewing files that changed from the base of the PR and between 93f2493 and 9ee6b6c.

📒 Files selected for processing (4)
  • packages/opencode/src/automation/index.ts
  • packages/opencode/src/automation/scheduler.ts
  • packages/opencode/test/server/automation-runner.test.ts
  • packages/opencode/test/server/automation-scheduler.test.ts
📝 Walkthrough

Walkthrough

Converts automation definitions and runs from in-memory caches to durable DB tables, implements cron scheduling with timezone-aware next-fire computation, and adds distributed scheduler ownership and run leasing (Flock). API routes now await scheduler ownership settlement and error mappings return 409 for conflicts/active-run ownership.

Changes

Automation Durability & Cron Scheduling

Layer / File(s) Summary
Database schema and ORM definitions
packages/opencode/migration/20260530170000_automation_persistence/migration.sql, packages/opencode/src/automation/automation.sql.ts, packages/opencode/src/storage/schema.ts, packages/opencode/package.json
Adds automation_definition and automation_run migration and Drizzle table schemas with FK cascades and indexes; adds luxon dev/runtime deps.
Non-blocking flock helper
packages/opencode/src/util/flock.ts
Adds tryAcquire to attempt zero-timeout lock acquisition and return an optional Lease.
Core imports, errors, and module state
packages/opencode/src/automation/index.ts
Updates imports, adds Zod error response schemas and exported Conflict/ActiveRunStillRunning error classes, and removes in-memory definition/run caches.
Definition & run DB IO and CRUD
packages/opencode/src/automation/index.ts
Implements writeDefinition/replaceDefinition, writeRun, getRun, and persists create/list/get/update/remove operations with revision-checked transactions.
Run execution, stopping, and reconciliation
packages/opencode/src/automation/index.ts
Makes run execution acquire per-run leases, reload latest run state before transitions, enforces durable writer ownership, reconciles stale active runs transactionally, and reimplements runs pagination.
Scheduler: cron, timezone, and ownership
packages/opencode/src/automation/scheduler.ts
Parses cron schedules (timezone-aware next-fire computation), includes timezone in schedule equality, and adds lease-based distributed ownership (becomeOwner/settleOwner/scan) gating scheduling and rescans.
Server routes: settle owner and error mapping
packages/opencode/src/server/instance/automation.ts
Routes now await scheduler.settleOwner() before operations; ConflictError/ActiveRunStillRunningError map to HTTP 409; delete flow settles owner before canceling runs.
Tests: durability, reconciliation, ownership, cron semantics
packages/opencode/test/server/*
Expands tests to cover restart durability, reconciliation of persisted active runs, lease-based ownership behavior, cron semantics (day/month/week logic, timezone), and updates many tests to await async execution paths and use new helpers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Astro-Han/pawwork#983: Related earlier work on run execution lifecycles and run-now/execute changes that are now persisted durably.
  • Astro-Han/pawwork#960: Prior in-memory automation contract and execution core that this PR refactors to DB-backed persistence.
  • Astro-Han/pawwork#984: Related scheduler timer and stop/cancel behavior extended here with cron support and ownership coordination.

"🐰 From cache to database, the automation dreams hop,
Cron ticks by timezone, owners hold leases nonstop,
Restarts keep their runs, reconciliation gets its due,
With Flock and DB in hand, schedules run true!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding persistence to the automation scheduler state, which is the core objective of this PR.
Description check ✅ Passed The PR description comprehensively covers all required sections: clear summary of changes, well-explained rationale, related issue linked, human review status set, specific review focus areas identified, meaningful risk notes provided, detailed verification steps with results included, and all applicable checklist items completed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/i950-automation-pr4-persistence

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested priority: P2 (includes non-doc, non-test paths outside the low-risk bucket).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request transitions the automation definitions and runs from in-memory maps to persistent SQLite tables using Drizzle ORM, and introduces cron-based recurring schedules along with a distributed locking mechanism using Flock to prevent concurrent scheduler executions. The review feedback highlights several critical performance issues: hasDurableActiveWriter performs N+1 database queries inside a loop under an active transaction, computeNextCronFireAt repeatedly parses cron expressions and instantiates sets inside a large loop causing CPU overhead, and functions like list, runs, and reconcileInterruptedRuns fetch entire collections into memory to filter them instead of utilizing targeted SQL queries.

Comment thread packages/opencode/src/automation/index.ts
Comment thread packages/opencode/src/automation/scheduler.ts Outdated
Comment thread packages/opencode/src/automation/index.ts
Comment thread packages/opencode/src/automation/index.ts Outdated
Comment thread packages/opencode/src/automation/index.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/src/automation/index.ts (1)

798-807: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Prevent misclassifying executor failures as cancelled when the run is still scheduled

  • executeRun waits for await executor(...) before advancing scheduled to running, but its catch path treats any error while current.state === "scheduled" as stopReason: "cancelled" (packages/opencode/src/automation/index.ts around 830-837).
  • sessionPromptExecutor only calls Automation.markRunStarted(...) after await Session.create(...), so failures during pre-start steps can leave the run scheduled and then be mislabeled as cancelled (packages/opencode/src/automation/runner.ts).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/automation/index.ts` around lines 798 - 807, The
executor error path is incorrectly converting pre-start failures into a
"cancelled" stop for runs that are still "scheduled"; update executeRun's catch
logic so it does not call stopRun(..., "cancelled") for errors when
latest/current state is "scheduled" (only treat controller.signal.aborted as
cancellation), and instead mark those runs as failed/errored or leave them
scheduled until an explicit start occurs; additionally, in sessionPromptExecutor
move or add the call to Automation.markRunStarted(...) to occur before any
awaited pre-start operations (e.g., before Session.create) so that start-related
failures are attributed to a running run rather than a scheduled one—use the
symbols executor, executeRun, getRun, stopRun, publishRunUpdated,
markRunStarted, sessionPromptExecutor, controller.signal to locate and change
the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/automation/index.ts`:
- Around line 858-885: The runs() function materializes the entire ledger by
calling allRuns() which uses .all() then slices; change this to push
cursor/limit into the DB query in allRuns(automationID) so SQL returns only the
requested page. Modify allRuns to accept limit and cursor (or cursor fields) and
implement a DB query that applies ordering (desc triggered_at, desc id), uses a
cursor predicate to fetch rows after the cursor (compare triggered_at and id)
and a limit of requestedLimit+1 to detect nextCursor, removing the in-memory
.all() + slice; update runs() to call allRuns(automationID, limit, cursor) and
compute nextCursor from the extra row. Ensure symbols referenced: runs, allRuns,
Database.use, AutomationRunTable, .orderBy, and remove the post-query .all().
- Around line 456-478: The durable write in writeDefinition (and the similar
block at lines 481-510) must use optimistic concurrency: instead of blind upsert
by primary key, perform a compare-and-swap on the stored revision/state (e.g.,
data.revision or a dedicated revision column) inside the same Database.use
transaction so the update only applies if the current row's revision matches the
expected previous revision; if it doesn't match, abort/throw so the caller can
retry or reconcile. Locate the AutomationDefinitionTable upsert in
writeDefinition and change the onConflict/update to include a WHERE or
conditional that checks AutomationDefinitionTable.data->>'revision' (or
AutomationDefinitionTable.revision) equals the expected prior revision from the
Definition being written, increment the revision on success, and apply the same
pattern to the other durable write blocks referenced (lines ~481-510) so all
state transitions use compare-and-swap semantics and surface a conflict error
when revisions diverge.

In `@packages/opencode/src/automation/scheduler.ts`:
- Around line 120-145: cronValues and parseCronSchedule must reject malformed
cron input before iterating to avoid infinite loops and blowups (e.g. "*/0",
negative steps, bad ranges, too few/too many fields); update parseCronSchedule
to validate the expression has exactly five whitespace-separated fields, and in
cronValues validate that stepRaw parses to an integer > 0, that range bounds are
numeric and within [min,max], that start <= end, and that wildcard/step combos
produce a sensible start/end; on any invalid condition throw a clear error so
the caller (e.g. settleOwner) can skip/bail rather than entering a
non-terminating loop or producing huge sets.

In `@packages/opencode/src/server/instance/automation.ts`:
- Around line 239-243: Move scheduler ownership settlement before deleting the
automation: call AutomationScheduler.current() and await scheduler.settleOwner()
first, then call Automation.remove(...) to produce the tombstone, and finally
call scheduler.cancel(removed.tombstone.id); ensure you preserve the same
identifiers (Automation.remove, AutomationScheduler.current,
scheduler.settleOwner, scheduler.cancel, removed.tombstone.id) and await the
settleOwner promise before performing the remove so ownership reconciliation
happens prior to the tombstone flow.

In `@packages/opencode/test/server/automation-routes.test.ts`:
- Around line 202-220: The test currently stubs AutomationScheduler.install with
settleOwner flipping a synchronous flag, which only proves it was called not
awaited; change the stub for settleOwner in the test "list route waits for
scheduler owner settle..." (and the similar test at 224-248) to return a
deferred Promise (e.g., create a resolve function outside the stub, have
settleOwner return that Promise) so the HTTP request remains pending until you
explicitly call the resolver, then assert that settled remains false before
resolving and true after resolving, confirming the route actually awaited
settleOwner (refer to AutomationScheduler.install and settleOwner in the test).

---

Outside diff comments:
In `@packages/opencode/src/automation/index.ts`:
- Around line 798-807: The executor error path is incorrectly converting
pre-start failures into a "cancelled" stop for runs that are still "scheduled";
update executeRun's catch logic so it does not call stopRun(..., "cancelled")
for errors when latest/current state is "scheduled" (only treat
controller.signal.aborted as cancellation), and instead mark those runs as
failed/errored or leave them scheduled until an explicit start occurs;
additionally, in sessionPromptExecutor move or add the call to
Automation.markRunStarted(...) to occur before any awaited pre-start operations
(e.g., before Session.create) so that start-related failures are attributed to a
running run rather than a scheduled one—use the symbols executor, executeRun,
getRun, stopRun, publishRunUpdated, markRunStarted, sessionPromptExecutor,
controller.signal to locate and change the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b66a6d3b-49d2-4cd4-9da5-26917a7cd83c

📥 Commits

Reviewing files that changed from the base of the PR and between 29d63da and 6de4ebc.

📒 Files selected for processing (11)
  • packages/opencode/migration/20260530170000_automation_persistence/migration.sql
  • packages/opencode/package.json
  • packages/opencode/src/automation/automation.sql.ts
  • packages/opencode/src/automation/index.ts
  • packages/opencode/src/automation/scheduler.ts
  • packages/opencode/src/server/instance/automation.ts
  • packages/opencode/src/storage/schema.ts
  • packages/opencode/src/util/flock.ts
  • packages/opencode/test/server/automation-routes.test.ts
  • packages/opencode/test/server/automation-runner.test.ts
  • packages/opencode/test/server/automation-scheduler.test.ts

Comment thread packages/opencode/src/automation/index.ts
Comment thread packages/opencode/src/automation/index.ts
Comment thread packages/opencode/src/automation/scheduler.ts
Comment thread packages/opencode/src/server/instance/automation.ts Outdated
Comment thread packages/opencode/test/server/automation-routes.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/opencode/src/automation/index.ts`:
- Around line 628-656: The replaceRun function currently returns next when the
DB row is missing or owned by another project, which silently signals success;
update the check inside replaceRun (the block that reads AutomationRunTable and
tests row/project_id/owner_directory) to instead surface the failure—either
throw the same NotFoundError used by replaceDefinition or return previous to
indicate no change; ensure the change occurs before any DB update and keep the
revision-conflict behavior (returning current) unchanged so callers can
distinguish not-found vs. conflict cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d9dd494b-69fb-4a3d-8109-24571b0e65a3

📥 Commits

Reviewing files that changed from the base of the PR and between 6de4ebc and 93f2493.

⛔ Files ignored due to path filters (2)
  • packages/sdk/js/src/v2/gen/sdk.gen.ts is excluded by !**/gen/**
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (6)
  • packages/opencode/src/automation/index.ts
  • packages/opencode/src/automation/scheduler.ts
  • packages/opencode/src/server/instance/automation.ts
  • packages/opencode/test/server/automation-routes.test.ts
  • packages/opencode/test/server/automation-runner.test.ts
  • packages/opencode/test/server/automation-scheduler.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/opencode/src/server/instance/automation.ts
  • packages/opencode/test/server/automation-routes.test.ts
  • packages/opencode/src/automation/scheduler.ts
  • packages/opencode/test/server/automation-scheduler.test.ts

Comment thread packages/opencode/src/automation/index.ts
@Astro-Han Astro-Han merged commit 30f3417 into dev May 30, 2026
27 checks passed
@Astro-Han Astro-Han deleted the codex/i950-automation-pr4-persistence branch May 30, 2026 17:27
Astro-Han added a commit that referenced this pull request Jun 2, 2026
Closes the backend gaps left after PR1-5 so the PR6/PR7 frontend slice can
build on a complete, stable automation contract.

Goal / change boundary:
- model { providerID, modelID } now required on every AutomationDefinition;
  runner passes it through so runs no longer depend on the runtime model
  fallback chain (which drifts across restarts).
- variant? optional reasoning/effort, validated against the live provider
  catalog on create/update; invalid variant returns 422 instead of failing
  late in the run.
- stop.kind === "condition" rejected at create/update with structured
  { field: "stop", message: "unsupported_stop_condition" } (scheduler never
  schedules condition stops, so accepting them only leaked dead UI surface).
- Derived fields nextFireAt / nextFires / failureStreak populated at
  create/update and refreshed after every terminal run; scheduler re-publishes
  automation.definition.updated with a bumped revision for global-sync.
- cron validation consolidated into src/automation/cron.ts; scheduler and
  derived both consume it.
- automate tool schema picks up model/variant + Provider-backed create-time
  validation.
- Migration 20260601100000 drops pre-release rows so model can be NOT NULL.

Verification:
- CI green on 5e177ba (unit-opencode flake on an unrelated VCS-routes
  20s timeout cleared on rerun).
- Local: 129 automation tests (routes/runner/scheduler/event-fixtures/tool/
  cron) + 35 session processor tests pass.

Review follow-ups addressed:
- codex: scheduler self-loop guard keyed by id:revision (not id alone).
- P2: recordRunOutcome retries on ConflictError instead of silently dropping
  the run outcome.
- CodeRabbit: 422 create test uses a guaranteed-invalid model; fixed sleeps
  replaced with terminal-state polling.
- Test seam for the ConflictError path moved off the public Automation API
  into an internal __test_hooks module.

Residual risk / deferred (tracked, not blocking PR6):
- needs_user_input / loop_gate run-error codes remain reserved; producing
  them needs prompt-loop semantics changes.
- Session.automationID reverse lookup deferred (session-contract migration).
- stop=condition kept in the schema layer for SDK shape parity but rejected
  at validate time; collapses once a condition evaluator lands.

Linked: issue #950; follows PR #960 #983 #984 #998 #1004; unblocks PR6/PR7.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request harness Model harness, prompts, tool descriptions, and session mechanics P1 High priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant