diff --git a/services/global_chat/prompts.yaml b/services/global_chat/prompts.yaml index 4da24ee0..10712118 100644 --- a/services/global_chat/prompts.yaml +++ b/services/global_chat/prompts.yaml @@ -108,8 +108,26 @@ prompts: Job code is stitched into the workflow YAML by job_key — the workflow must exist first. 1. Create/modify workflow structure FIRST (`call_workflow_agent`) - 2. THEN generate job code (`call_job_code_agent`) — only for jobs already in the YAML - 3. Set `job_key` to the exact key from the workflow structure + 2. When you're building something new — a whole workflow, or a new step you're also + writing the code for — define the contract for each edge where one step passes data + to another, in system-agnostic terms: + - Label—one name for the passed data; give both the producing and consuming step + the same name. + - Ownership—one step produces/transforms it; downstream steps consume it as-is and + never re-derive it. + + Put the identical contract line in both `call_job_code_agent` messages. Describe what + flows and which step owns it—never the mechanism (state, return shape, JSONPath, + loops, adaptor functions); the job-code agent owns that. + + e.g. "Step A produces the fetched records; Step B consumes them as-is—don't re-fetch + or rebuild them." + + This only applies to work you are creating. When editing existing steps, make only + the change the user asked for—don't restate contracts, re-derive the data flow, or + make other changes they didn't request. + 3. THEN generate job code (`call_job_code_agent`) — only for jobs already in the YAML + 4. Set `job_key` to the exact key from the workflow structure You may call `call_job_code_agent` for multiple existing jobs in parallel. Never call `call_job_code_agent` and `call_workflow_agent` in the same step. diff --git a/services/global_chat/tests/acceptance/one_shot_workflows/test_rest_to_rest_sync_with_cron.md b/services/global_chat/tests/acceptance/one_shot_workflows/test_rest_to_rest_sync_with_cron.md new file mode 100644 index 00000000..a973260e --- /dev/null +++ b/services/global_chat/tests/acceptance/one_shot_workflows/test_rest_to_rest_sync_with_cron.md @@ -0,0 +1,92 @@ +--- +id: global-chat.rest-to-rest-sync-with-cron +service: global_chat +judges: [general, openfn_workflow_expert, openfn_code_quality] +--- + +# notes + +From-scratch scheduled REST-to-REST sync with fully specified job code. No existing YAML, no history. The user gives a precise spec: a daily cron trigger, an HTTP GET of a user list, a transform into a three-field shape (userId, title, body), and an HTTP POST of each transformed record. The planner should be invoked, call the workflow agent to produce the structure with a cron trigger, then call the job code agent to fill in the bodies. + +The key thing this test probes is data-flow coherence across steps: the transform step and the post step must agree on how the transformed records are passed between them. The steps should not read as if written in isolation — the downstream step must consume exactly what the upstream step produced, under the same name, without re-fetching or rebuilding it. + +The following workflow is a NON-BINDING reference showing one acceptable shape. Do not require the candidate to match it (adaptor versions, job names, whether GET and transform are one step or two, and the exact state key may all differ). Use it only to sanity-check that the candidate is a plausible, coherent solution. + +```yaml +name: " Daily REST Endpoint Sync (Manual)" +jobs: + Fetch-and-transform-users: + name: Fetch and transform users + adaptor: "@openfn/language-http@latest" + body: >- + + + get('https://jsonplaceholder.typicode.com/users'); + + + fn((state) => { + const users = state.data || []; + const records = users.map((user) => ({ + userId: user.id, + title: user.name, + body: `Email: ${user.email} | Company: ${user.company?.name ?? 'N/A'}`, + })); + console.log(`Transformed ${records.length} users`); + return { ...state, records }; + }); + Post-records-to-target: + name: Post records to target + adaptor: "@openfn/language-http@7.3.2" + body: > + each( + '$.records[*]', + post('https://jsonplaceholder.typicode.com/posts', (state) => state.data) + ); + + + fn((state) => { + console.log(`Posted ${state.records?.length ?? 0} records`); + return state; + }); +triggers: + cron: + type: cron + enabled: false + cron_expression: 0 0 * * * + cron_cursor_job: null +edges: + cron->Fetch-and-transform-users: + condition_type: always + enabled: true + target_job: Fetch-and-transform-users + source_trigger: cron + Fetch-and-transform-users->Post-records-to-target: + condition_type: on_job_success + enabled: true + target_job: Post-records-to-target + source_job: Fetch-and-transform-users +``` + +# quality_criteria + +- The workflow uses a cron trigger scheduled to run once a day (e.g. a `0 0 * * *` daily expression), not a webhook or a different frequency. +- A step fetches the user list from the source endpoint (`https://jsonplaceholder.typicode.com/users`) using an HTTP get. +- A transform maps each user into an object with exactly the three requested fields: `userId` (the user's id), `title` (the user's name), and `body` (a string combining the user's email and company name). +- A step POSTs each transformed record to the target endpoint (`https://jsonplaceholder.typicode.com/posts`) using an HTTP post. +- Data-flow coherence: the posting step consumes the exact data the transform step produced, referencing it under the same state key/name that the transform step wrote to. There is no key mismatch between the producing and consuming steps. +- The posting step does not re-fetch the users or rebuild the transformed objects itself — it consumes the upstream output as-is rather than duplicating the transform. +- The solution stays simple as requested: no branching, filtering, deduplication, or auth logic beyond what the user asked for. + +# turn + +## role + +user + +## content + +Build a scheduled workflow that copies records between two REST endpoints. +Trigger: cron, once a day. +Steps: GET the list of users from https://jsonplaceholder.typicode.com/users Transform each user into a smaller object with three fields: userId (the user's id), title (the user's name), and body (a short string combining their email and company name). POST each transformed record to https://jsonplaceholder.typicode.com/posts + +No authentication is required for this API. Keep it simple: no branching or deduplication.