Skip to content

feat: Add preset_metadata JSON field to Automation model#89

Open
malhotra5 wants to merge 3 commits into
mainfrom
openhands/add-preset-metadata-field
Open

feat: Add preset_metadata JSON field to Automation model#89
malhotra5 wants to merge 3 commits into
mainfrom
openhands/add-preset-metadata-field

Conversation

@malhotra5
Copy link
Copy Markdown
Member

@malhotra5 malhotra5 commented Apr 30, 2026

Summary

Adds a generic preset_metadata JSON column to the Automation model that stores preset-specific configuration for UI consumption. This follows up on the repository support work and allows the UI to display automation configuration without needing to inspect tarball contents.

Closes #56

Changes

Model Changes

  • automation/models.py: Added preset_metadata: Mapped[dict | None] JSON column (nullable)

Schema Changes

  • automation/schemas.py: Added preset_metadata: dict | None to AutomationResponse

Endpoint Changes

  • automation/preset_router.py:
    • Prompt preset endpoint now populates preset_metadata with:
      • preset_type: "prompt"
      • prompt: the user's prompt text
      • repos: (optional) repository configuration when repos are specified
    • Plugin preset endpoint now populates preset_metadata with:
      • preset_type: "plugin"
      • prompt: the user's prompt text
      • plugins: list of plugin source configurations
      • repos: (optional) repository configuration when repos are specified

Migration

  • migrations/versions/005_add_preset_metadata_column.py: Adds the preset_metadata JSON column

Tests

  • tests/test_preset_router.py: Added TestPresetMetadata class with 5 comprehensive tests:
    • test_prompt_preset_sets_preset_metadata
    • test_prompt_preset_with_repos_includes_repos_in_metadata
    • test_plugin_preset_sets_preset_metadata
    • test_plugin_preset_with_repos_includes_repos_in_metadata
    • test_preset_metadata_stored_in_database

Metadata Schema

{
  "preset_type": "prompt" | "plugin",
  "prompt": "...",
  "plugins": [{"source": "github:owner/repo", "ref": "v1.0.0"}],
  "repos": [{"url": "OpenHands/repo", "ref": "main"}]
}
  • Prompt presets: preset_type, prompt, repos (when specified)
  • Plugin presets: preset_type, prompt, plugins, repos (when specified)
  • Custom SDK automations: preset_metadata = NULL

Design Decision: sa.JSON vs JSONB

This field uses sa.JSON instead of PostgreSQL-specific JSONB to support potential future SQLite compatibility. Existing fields (trigger, event_payload) still use JSONB as they were created before this requirement.

Testing

All unit tests pass. Integration tests require Docker (skipped in this environment but should pass in CI).


This PR was created by an AI agent (OpenHands) on behalf of the user.

- Add preset_metadata JSONB column to Automation model (nullable)
- Add preset_metadata to AutomationResponse schema
- Populate preset_metadata in prompt preset endpoint with:
  - preset_type: 'prompt'
  - prompt: the user's prompt
  - repos: (optional) repository configuration
- Populate preset_metadata in plugin preset endpoint with:
  - preset_type: 'plugin'
  - prompt: the user's prompt
  - plugins: list of plugin sources
  - repos: (optional) repository configuration
- Add Alembic migration 005 for the new column
- Add comprehensive tests for preset_metadata feature

This allows the UI to display preset-specific configuration
without needing to inspect tarball contents.

Closes #56

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

Coverage

Coverage Report
FileStmtsMissCoverMissing
__init__.py10100% 
app.py1185950%36, 39, 42, 48, 50, 53, 56–59, 62–63, 66, 73–74, 77–78, 82, 90–91, 94, 101–102, 104, 107–108, 111, 116–124, 126–128, 214–216, 221–222, 224–225, 227, 230–232, 234–235, 237–238, 243–244, 248, 251, 253
auth.py116595%80, 122, 286, 294–295
config.py1460100% 
constants.py160100% 
db.py442640%37–39, 48–49, 51–52, 54, 62, 69, 79, 82–83, 87–88, 96, 104, 109, 114, 117–123
dispatcher.py1393574%53, 65, 67–68, 137, 141, 163, 171, 190–192, 220–222, 225–227, 280–281, 305–312, 328, 341–342, 355–356, 363–364, 366
event_router.py591967%83, 88, 119–121, 137–138, 156, 158, 160–161, 163, 173, 179–181, 184, 186, 188
exceptions.py40100% 
execution.py24414739%36–38, 73–76, 84–88, 90, 98–100, 105–109, 111, 125–128, 130, 132, 134–137, 139–144, 146, 148–155, 157–158, 160, 196–198, 204–206, 217–220, 226–228, 268–272, 281, 289, 293, 295–296, 301–302, 307, 359, 361, 379, 382–385, 405–408, 410, 418–419, 422, 428, 491–493, 495–501, 504–505, 507, 509–511, 514, 517, 520–523, 525, 528–529, 532–534, 538–539, 543–546, 548, 556–557, 561–563, 565–571, 575, 577, 586–588, 590–592
filter_eval.py50296%161–162
logger.py551769%37, 50–51, 53–59, 74, 77, 101, 103–106
models.py810100% 
preset_router.py1885869%126, 234–235, 240–247, 252, 255, 258, 262–263, 265–266, 278–281, 283–287, 292, 301, 364, 475–476, 481–488, 493, 496, 499, 504–505, 507–508, 520–523, 525–529, 534, 544
router.py1136443%74–75, 95, 97, 100, 102, 116, 129, 131–132, 134–135, 138–140, 151–153, 171–174, 193, 196, 199, 206, 208, 237, 242–244, 247–249, 253–254, 259, 263–266, 268, 276, 278–279, 284–285, 288, 290, 292–294, 297–300, 305, 307–308, 317, 338–340, 344
scheduler.py57984%124–125, 162–163, 178–179, 189–190, 192
schemas.py2661793%31, 165, 171–173, 232–234, 236, 319–320, 323, 328, 333, 477, 485, 492
trigger_matcher.py28389%72–74
uploads.py1075944%138–141, 149–151, 157–158, 161, 170–171, 174–175, 183–184, 186–189, 192–195, 197, 199–201, 203–206, 208–209, 211, 226, 232–233, 236, 239, 242, 245, 247, 260–261, 275, 278–280, 282–283, 285, 291–292, 305, 313–315, 319
watchdog.py1004753%50–51, 63–64, 69–71, 83–84, 228–229, 231, 233, 242, 244–246, 248, 255–257, 259–261, 263–264, 266, 281, 283, 288–291, 293–298, 300–306, 308
webhook_router.py804840%57, 82–83, 107–108, 110, 113–114, 116, 126, 128–132, 137, 139, 151, 154, 157, 164–165, 167, 180, 182–183, 188, 204, 206–207, 213–215, 217–218, 220, 235, 237–238, 243–244, 261, 263–264, 270–271, 273, 275
event_schemas
   __init__.py29196%53
   custom.py33584%52–53, 64–66
   detection.py320100% 
   github.py125496%306, 311, 456, 483
presets
   __init__.py00100% 
storage
   __init__.py50100% 
   factory.py11190%29
   file_store.py22577%21, 30, 35, 40, 64
   google_cloud.py721184%49, 97–102, 136–137, 190, 192
   s3.py1121586%56, 100, 102–103, 107, 109, 190, 213–215, 269–270, 275, 337–338
utils
   __init__.py50100% 
   api_key.py322425%40–41, 46–48, 50, 55, 60, 62–65, 67–68, 70–71, 73, 79, 81–82, 89, 91–92, 98
   cron.py45686%39, 45, 74, 80, 123, 140
   log_context.py100100% 
   run.py771284%74–76, 175–177, 182–184, 231, 237–238
   sandbox.py1017624%40–41, 46–49, 51–53, 55–61, 73, 75, 85–86, 88–90, 92–93, 96–97, 103, 109–111, 121–122, 127–133, 156–157, 159–163, 165–169, 206–207, 209, 211–214, 219–220, 223, 225–226, 232–234, 239–241, 246–247, 255–257, 259
   tarball_validation.py480100% 
   time.py30100% 
   webhook.py511668%46, 51, 119, 129–131, 137, 174, 177–183, 189
TOTAL282579172% 

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

🚀 Deploy Preview PR Created/Updated

A deploy preview has been created/updated for this PR.

Deploy PR: https://github.com/OpenHands/deploy/pull/3996
Automation SHA: 5d18ea70710e0bd7b057035a31c5a6c3d51f20f2
Last updated: Apr 30, 2026, 11:12:53 AM ET

Once the deploy PR's CI passes, the automation service will be deployed to the feature environment.

Change preset_metadata column from PostgreSQL-specific JSONB to
generic sa.JSON to support potential future SQLite usage.

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
@malhotra5 malhotra5 marked this pull request as ready for review April 30, 2026 15:16
Copy link
Copy Markdown

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

🟡 Acceptable with fixes - Good structure and comprehensive tests, but needs one critical fix for type consistency.

Verdict:Needs rework - Fix the JSON→JSONB type inconsistency before merging.

Key Insight: Using JSON instead of JSONB contradicts the PR description, breaks consistency with existing fields, and sacrifices PostgreSQL-specific performance benefits.

Comment thread automation/models.py
Comment thread automation/models.py
Comment thread migrations/versions/005_add_preset_metadata_column.py
@all-hands-bot
Copy link
Copy Markdown

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟡 MEDIUM

The PR is additive and backward-compatible (nullable field), with good test coverage. However, the JSON vs JSONB type inconsistency could cause issues:

  • Performance impact: JSON stores text, JSONB stores binary - queries on this field will be slower than expected
  • Indexing limitations: JSONB supports GIN indexes for fast queries; JSON does not
  • Inconsistency risk: Different behavior than other JSON fields in the schema

Once the type is corrected to JSONB, this becomes LOW risk.


Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  2. Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it's merge-ready.

Copy link
Copy Markdown
Member Author

Addressed review feedback — the use of sa.JSON instead of JSONB is intentional per maintainer request for future SQLite compatibility. Resolved all threads with explanations. Ready for another review.

@malhotra5 malhotra5 requested a review from all-hands-bot April 30, 2026 15:22
Copy link
Copy Markdown

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

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

🟢 Good taste - Clean, pragmatic implementation that solves a real problem.

The preset_metadata field provides the UI with structured configuration data without requiring tarball inspection. Implementation is straightforward: nullable JSONB column, populated by preset endpoints, comprehensive tests covering all scenarios.

The intentional use of sa.JSON (vs JSONB for older fields) maintains SQLite compatibility per maintainer guidance from resolved review threads.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟢 LOW

Adds a nullable column with no breaking changes. Only affects preset endpoints. Comprehensive test coverage verifies metadata population and database persistence. Safe migration strategy (nullable column compatible with existing records).

VERDICT:
Worth merging - Simple, well-tested addition that enables UI functionality.

KEY INSIGHT:
Storing preset metadata alongside the automation eliminates the need for UI to parse tarballs, enabling instant configuration display—a pragmatic separation of runtime artifacts from UI state.

@malhotra5 malhotra5 changed the title feat: Add preset_metadata JSONB field to Automation model feat: Add preset_metadata JSON field to Automation model Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add generic preset metadata field to Automation model

3 participants