Skip to content

feat(cron): add conversational update tool - #1057

Merged
dennisonbertram merged 6 commits into
mainfrom
codex/issue-1002-conversational-crud
Aug 1, 2026
Merged

feat(cron): add conversational update tool#1057
dennisonbertram merged 6 commits into
mainfrom
codex/issue-1002-conversational-crud

Conversation

@dennisonbertram

@dennisonbertram dennisonbertram commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Closes #1002

Outcome

Completes model-facing conversational cron CREATE, READ, UPDATE, PAUSE, RESUME, HISTORY, and DELETE while preserving legacy shell jobs. Harness jobs are immutably bound to the active tenant, conversation, and agent, and scheduled fires start new runs in that same persisted conversation.

This promotion candidate is 2709fa1a, based on origin/main 3506e01c997231c46920b45bf8947c50087dd863.

Scope and architecture

  • Registers all eight cron tools in the assembled core catalog.
  • Adds typed harness-prompt creation and update while retaining shell-command compatibility.
  • Applies one idempotent model-facing scope wrapper at NewDefaultRegistryWithOptions, covering embedded and remote adapters without changing raw operator access.
  • Keeps model CRUD/history ID-only. Operator lookup uses GET /v1/jobs/by-name?name=... and returns a typed ambiguity error.
  • Requires the updated_at version returned by cron_get for update, pause, resume, and delete.
  • Returns explicit history availability/warning fields so a database failure cannot look like a valid empty history.

Persistence and scheduler correctness

  • Replaces global name uniqueness with active-name uniqueness on (tenant_id, conversation_id, agent_id, name).
  • Migrates legacy inline, named, quoted, bracketed, backtick, and collated single-column uniqueness transactionally using SQLite index metadata.
  • Preserves jobs, executions, timestamps, foreign keys, legacy empty-scope rows, and integrity across an idempotent second migration.
  • Active schedule replacement uses inert Prepare -> durable CAS -> infallible Commit, retaining the old live/durable state when prepare or CAS fails.
  • Create and paused-to-active resume use paused-first registration so failures remain restart-safe.
  • Scheduler admission revalidates a monotonic registration identity after jitter and under the same lock as execution creation, preventing stale callbacks from firing after a completed lifecycle transition.
  • Prompt/config/timeout/tag-only edits do not replace the live schedule entry.

Validation and provider compatibility

  • Shell jobs require a non-empty command and harness jobs require a non-empty prompt.
  • Explicit non-positive timeouts and invalid schedules/configurations fail before persistence.
  • cron_create exposes a provider-compatible object-root schema; runtime validation retains the shell/harness exclusivity contract without top-level oneOf/anyOf/allOf.
  • The assembled-registry regression validates every visible core tool schema and explicitly requires all eight cron tools.

Test-first evidence

Deterministic red tests covered cross-scope access, same-name isolation, stale update/pause/resume/delete, duplicate scheduler entries, registration failure, scheduler/store divergence, update/delete races, stale callback admission, semantic migration detection, ambiguous operator lookup, history unavailability, and provider schema rejection. Each red was observed before its repair.

Focused normal and race packages pass:

go test ./internal/cron ./internal/harness ./internal/harness/tools ./internal/harness/tools/deferred ./cmd/harnessd -count=1
go test -race ./internal/cron ./internal/harness ./internal/harness/tools ./internal/harness/tools/deferred ./cmd/harnessd -count=1

The authoritative logged-in foreground gate passed on exact candidate 2709fa1a:

DEVELOPER_DIR=/Library/Developer/CommandLineTools TMPDIR=/private/tmp GOCACHE=<isolated> ./scripts/test-regression.sh
coveragegate: PASS (total=85.6%, min=80.0%, zero-functions=0)
[regression] PASS

Real provider and conversation proof

A real harnessd canary using OpenAI gpt-4.1-mini exercised all eight model-facing cron tools in one persisted conversation:

  • 11 runs completed with the exact tenant/conversation/agent tuple.
  • The created cron fired twice as distinct scheduler-started runs.
  • Both fires emitted assistant.message into conversation SSE and appeared in the same transcript.
  • A stale update version was rejected; a fresh update changed the prompt and moved the schedule to 2027.
  • cron_get/history returned both executions with linked run IDs.
  • Pause and resume changed durable state.
  • Versioned delete ended with jobs: [] and HTTP 404 for the former ID.

This proves the harness/API conversation path for #1002. Real PTY TUI, rendered macOS GUI, remote cronsd authentication/dispatch, terminal run linkage/no-overlap, and callback behavior remain separate epic children and are not claimed here.

Rollout and rollback

Back up the stopped SQLite database before first startup. The migration is atomic and idempotent; verify row counts, execution references, integrity_check, and foreign_key_check before accepting traffic. Roll back with a compatibility-capable prior binary while retaining the migrated schema. Do not downgrade to global name uniqueness when cross-scope duplicates exist.

Promotion checklist

  • Structured child issue with acceptance criteria, scope, impact map, TDD plan, and rollback
  • Exact focused normal/race tests
  • Exact authoritative full regression
  • Real-provider all-eight-tool same-conversation CRUD/fire canary
  • Exact-head frontier review repaired and cheap exact-diff re-review clear
  • Exact candidate pushed to this PR
  • Fresh hosted checks green
  • Production merge gate green and merged to main

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9535a45d26

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ExecConfig: args.ExecConfig,
TimeoutSec: args.TimeoutSec,
Tags: args.Tags,
ExpectedUpdatedAt: expectedUpdatedAt,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Enforce stale tokens in the embedded adapter

When HARNESS_CRON_URL is unset, buildCronBootstrap supplies embeddedCronAdapter as this client, but that adapter's UpdateJob never examines req.ExpectedUpdatedAt. If a job changes after cron_get, an update carrying the stale timestamp therefore still succeeds and overwrites the newer row in the default embedded deployment; enforce the token with an atomic conditional store update on this path.

Useful? React with 👍 / 👎.

Comment thread internal/cron/server.go Outdated
writeError(w, http.StatusBadRequest, "invalid_json", err.Error())
return
}
if req.ExpectedUpdatedAt != nil && !job.UpdatedAt.Equal(req.ExpectedUpdatedAt.UTC()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Compare the version atomically with the write

When two remote PATCH requests read the same job version before either writes, both pass this check, and SQLiteStore.UpdateJob later updates solely by job_id, allowing the second full-row write to silently overwrite the first despite its supplied token. Put the expected timestamp predicate in the update transaction or WHERE clause and return 409 when no row matches.

Useful? React with 👍 / 👎.

Comment thread internal/harness/tools/deferred/cron.go Outdated
"schedule": map[string]any{"type": "string", "description": "New 5-field UTC cron expression"},
"command": map[string]any{"type": "string", "description": "New shell command; encoded as execution_config"},
"execution_config": map[string]any{"type": "string", "description": "New execution config JSON"},
"timeout_seconds": map[string]any{"type": "integer", "description": "New timeout in seconds"},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject nonpositive timeout updates

When the model supplies timeout_seconds as zero or a negative number, the schema and handler accept it and both update paths persist it unchanged. ShellExecutor derives its execution deadline from this value, so subsequent runs time out immediately; require a minimum of one second in the schema and runtime validation.

Useful? React with 👍 / 👎.

Comment thread docs/logs/engineering-log.md Outdated
Comment on lines +12 to +18
- Full-gate result: `./scripts/test-regression.sh` reaches all packages but
fails only in the pre-existing real-Keychain tests
`TestKeychainRoundTripAgainstRealKeychain` and
`TestSavingAKeyForAnExistingProviderMakesItUsable`; the child `security`
helper prompts and is killed after 15 seconds. Running those two tests in
isolation passes. Do not report the full regression gate as green until
that environment/test-harness failure is resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep this change blocked until regression is green

The added verification evidence explicitly records that ./scripts/test-regression.sh remains red, so this commit lacks the repository-required full-gate proof. The repository treats any failing regression, including a pre-existing failure, as a blocker; resolve the Keychain failure and rerun the complete gate before accepting this change.

AGENTS.md reference: AGENTS.md:L32-L35

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

@codex review

Please review exact post-rebase head 9e20e53c0bd7a184871aa65f33d4969ce5c29852. The independent gate found and repaired model-facing cross-scope cron access, added typed harness prompt updates plus the timeout schema minimum, and passed affected normal tests, focused race tests, and the unchanged foreground ./scripts/test-regression.sh (85.7% coverage; zero-functions=0).

Do not treat #1054 or #1064 as waived: both upstream fixes must land, be rebased here, and hosted checks must pass before merge.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e20e53c0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/cron/server.go Outdated
Comment on lines +281 to +284
if job.Status == StatusPaused {
s.scheduler.RemoveJob(job.ID)
} else if err := s.scheduler.UpdateJobSchedule(job); err != nil {
writeError(w, http.StatusInternalServerError, "scheduler_error", err.Error())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Serialize scheduler reconciliation with the CAS write

When two updates overlap, writer A can commit version A, writer B can then read and commit version B and reconcile the scheduler, and writer A can finally execute this call with its stale snapshot. If either update changes the schedule—or if a delete occurs between A's CAS and reconciliation—the live scheduler is left on the older schedule or re-registers a deleted job even though persistence contains the newer state. The mirrored embedded path has the same ordering; serialize reconciliation per job or verify/reload the committed version before applying it.

AGENTS.md reference: AGENTS.md:L57-L61

Useful? React with 👍 / 👎.

Comment thread internal/cron/server.go Outdated
Comment on lines +281 to +284
if job.Status == StatusPaused {
s.scheduler.RemoveJob(job.ID)
} else if err := s.scheduler.UpdateJobSchedule(job); err != nil {
writeError(w, http.StatusInternalServerError, "scheduler_error", err.Error())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid rescheduling jobs for non-schedule edits

When an active job receives only a prompt, command, timeout, or tag update, this branch still calls UpdateJobSchedule, which removes and re-adds the cron entry. NextRunAt is not recomputed for those requests, so the returned and persisted next-run value can describe the old entry while the live scheduler computes from the re-registration time, potentially skipping an imminent occurrence. Since fireJob already reloads the current record, only schedule or status transitions should replace the live entry.

AGENTS.md reference: AGENTS.md:L57-L61

Useful? React with 👍 / 👎.

@dennisonbertram
dennisonbertram force-pushed the codex/issue-1002-conversational-crud branch from 9e20e53 to 2709fa1 Compare August 1, 2026 14:50
@dennisonbertram
dennisonbertram merged commit 2709fa1 into main Aug 1, 2026
2 checks passed
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.

[Change]: Let agents create and update conversation-bound cron jobs

1 participant