On 2026-10-13 the owner asked that the assistant be able to use and create saved bulk load profiles. This PR adds the tools from spec QRun-IO/qqq#1171 section 3.4. Every call goes through the /qqq/v1 UI API as the user (tools/bulkload).
Pending action, then on confirm: storeSavedBulkLoadProfile plus insertSharedRecord for each share
write
bulk_load_run(tableName, uploadRef, profileId)
Pending action, then on confirm: BulkLoadRunner, whose default is POST /processes/{t}.bulkInsert/jobs
write
Rules enforced by the server or the engine, not the model
PHI: the engine sets X-QQQ-AI-Route: hipaa|standard from the turn's provider (active.row().getHipaaEligible()). Core decides which samples and values to return. Tool-call logs drop sampleValues, example bullets and valueMappings.
Writes: writes use the existing pending-action path and run only on the user's Confirm. Both proposing and confirming require DataAccess READ_WRITE. Sharing is only as far as core lets the user share (owner check).
Attachments: files reach the model only as pageContext.attachments{uploadRef, fileName}. That is at most 5 entries of scalars, so no bytes.
Durable jobs seam:BulkLoadRunner.start(request) -> RunHandle{status (jobs_job status names), jobId, processUUID, summary}. Launches from the assistant are created AWAITING_CONFIRMATION by core.
Stand-ins
STAND-IN(core=#1179, removeBy=RC.8): the bulkLoad profile, suggestMapping and validate operations (QRun-IO/qqq#1179) are not in RC.5. Until they ship, the tools tell the user the server does not offer them yet. This uses gateway.isInvocable and makes no call.
STAND-IN(core=#1171, removeBy=RC.8): the jobs launch, from the durable-jobs kernel PR.
The upload itself is core's POST /qqq/v1/uploads, built by the jobs-kernel agent. The Next panel's attach button follows once that lands.
Shared files touched (additive; the #19/#21 rebase must keep these)
LoopbackUiApiGateway multipart bodies, plus forwarding of non-reserved headers.
UiApiToolbox.Outcome.of.
One branch each in ContextBuilder.tools, TurnEngine dispatch and AssistantApi.confirmAction.
sanitizePageContext attachments.
ToolLabels.
The AssistantQBitConfig.bulkLoadRunner seam.
Tests
BulkLoadToolsTest (6 tests):
Definitions are offered, and the write tools only when something is writable.
Read tools call the right operations with the user's session.
The route header always comes from the provider, even when the model's input contains X-QQQ-AI-Route.
404 maps to NOT_FOUND.
DataAccess denial makes no call.
An older server (operations not invocable) makes no call and returns a clear message.
The jobs runner says when the launch is missing.
forLog strips samples and values.
Golden eval (scripted model standing in for the LLM, run against a fake UI API that answers like core's dry run):
A CSV with messy headers is attached.
Attach, then suggest.
The dry run reports 2 rows missing required Status.
The model reads the error and adds defaultValue: DRAFT.
The FULL dry run reports 0 errors.
The model proposes a save shared with group kof22-admins. Nothing is saved until the user confirms; after Confirm, storeSavedBulkLoadProfile and insertSharedRecord run with the right values.
In a second turn, the model proposes the run. After Confirm, the runner receives profile 7 and up_1.
No sample sentinel appears in any tool-call log.
The full ./mvnw verify (all modules, checkstyle, JaCoCo gates, ArchitectureTest, NamingTest, golden and red-team evals) is green.
On 2026-10-13 the owner asked that the assistant be able to use and create saved bulk load profiles. This PR adds the tools from spec QRun-IO/qqq#1171 section 3.4. Every call goes through the `/qqq/v1` UI API as the user (`tools/bulkload`).
## Tools
| Tool | UI API | Kind |
|---|---|---|
| `bulk_profiles_list(tableName)` / `bulk_profile_describe(id)` | `POST /table/savedBulkLoadProfile/query`, `GET /table/savedBulkLoadProfile/{id}` (record locks apply) | read |
| `bulk_file_attach(uploadRef, tableName)` | `GET /bulkLoad/uploads/{ref}/profile` | read |
| `bulk_mapping_suggest(uploadRef, tableName)` | `POST /bulkLoad/{t}/suggestMapping`. The model then refines the result from the headers and the permitted samples. | read |
| `bulk_load_dry_run(uploadRef, tableName, profile, mode)` | `POST /bulkLoad/{t}/validate`. Nothing is stored. | read |
| `bulk_profile_save(tableName, label, profile, shares?)` | Pending action, then on confirm: `storeSavedBulkLoadProfile` plus `insertSharedRecord` for each share | write |
| `bulk_load_run(tableName, uploadRef, profileId)` | Pending action, then on confirm: `BulkLoadRunner`, whose default is `POST /processes/{t}.bulkInsert/jobs` | write |
## Rules enforced by the server or the engine, not the model
- **PHI:** the engine sets `X-QQQ-AI-Route: hipaa|standard` from the turn's provider (`active.row().getHipaaEligible()`). Core decides which samples and values to return. Tool-call logs drop `sampleValues`, example bullets and `valueMappings`.
- **Writes:** writes use the existing pending-action path and run only on the user's Confirm. Both proposing and confirming require DataAccess READ_WRITE. Sharing is only as far as core lets the user share (owner check).
- **Attachments:** files reach the model only as `pageContext.attachments` `{uploadRef, fileName}`. That is at most 5 entries of scalars, so no bytes.
- **Durable jobs seam:** `BulkLoadRunner.start(request) -> RunHandle{status (jobs_job status names), jobId, processUUID, summary}`. Launches from the assistant are created `AWAITING_CONFIRMATION` by core.
## Stand-ins
- `STAND-IN(core=#1179, removeBy=RC.8)`: the bulkLoad profile, suggestMapping and validate operations (QRun-IO/qqq#1179) are not in RC.5. Until they ship, the tools tell the user the server does not offer them yet. This uses `gateway.isInvocable` and makes no call.
- `STAND-IN(core=#1171, removeBy=RC.8)`: the jobs launch, from the durable-jobs kernel PR.
- The upload itself is core's `POST /qqq/v1/uploads`, built by the jobs-kernel agent. The Next panel's attach button follows once that lands.
## Shared files touched (additive; the #19/#21 rebase must keep these)
- `UiApiCall.withFormParam` / `withHeader`, mirroring core's `UiApiRequest.withFormParam`.
- `LoopbackUiApiGateway` multipart bodies, plus forwarding of non-reserved headers.
- `UiApiToolbox.Outcome.of`.
- One branch each in `ContextBuilder.tools`, `TurnEngine` dispatch and `AssistantApi.confirmAction`.
- `sanitizePageContext` attachments.
- `ToolLabels`.
- The `AssistantQBitConfig.bulkLoadRunner` seam.
## Tests
`BulkLoadToolsTest` (6 tests):
- Definitions are offered, and the write tools only when something is writable.
- Read tools call the right operations with the user's session.
- The route header always comes from the provider, even when the model's input contains `X-QQQ-AI-Route`.
- 404 maps to NOT_FOUND.
- DataAccess denial makes no call.
- An older server (operations not invocable) makes no call and returns a clear message.
- The jobs runner says when the launch is missing.
- `forLog` strips samples and values.
- **Golden eval** (scripted model standing in for the LLM, run against a fake UI API that answers like core's dry run):
1. A CSV with messy headers is attached.
2. Attach, then suggest.
3. The dry run reports 2 rows missing required Status.
4. The model reads the error and adds `defaultValue: DRAFT`.
5. The FULL dry run reports 0 errors.
6. The model proposes a save shared with group `kof22-admins`. Nothing is saved until the user confirms; after Confirm, `storeSavedBulkLoadProfile` and `insertSharedRecord` run with the right values.
7. In a second turn, the model proposes the run. After Confirm, the runner receives profile 7 and `up_1`.
8. No sample sentinel appears in any tool-call log.
The full `./mvnw verify` (all modules, checkstyle, JaCoCo gates, ArchitectureTest, NamingTest, golden and red-team evals) is green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
On 2026-10-13 the owner asked that the assistant be able to use and create saved bulk load profiles. This PR adds the tools from spec QRun-IO/qqq#1171 section 3.4. Every call goes through the
/qqq/v1UI API as the user (tools/bulkload).Tools
bulk_profiles_list(tableName)/bulk_profile_describe(id)POST /table/savedBulkLoadProfile/query,GET /table/savedBulkLoadProfile/{id}(record locks apply)bulk_file_attach(uploadRef, tableName)GET /bulkLoad/uploads/{ref}/profilebulk_mapping_suggest(uploadRef, tableName)POST /bulkLoad/{t}/suggestMapping. The model then refines the result from the headers and the permitted samples.bulk_load_dry_run(uploadRef, tableName, profile, mode)POST /bulkLoad/{t}/validate. Nothing is stored.bulk_profile_save(tableName, label, profile, shares?)storeSavedBulkLoadProfileplusinsertSharedRecordfor each sharebulk_load_run(tableName, uploadRef, profileId)BulkLoadRunner, whose default isPOST /processes/{t}.bulkInsert/jobsRules enforced by the server or the engine, not the model
X-QQQ-AI-Route: hipaa|standardfrom the turn's provider (active.row().getHipaaEligible()). Core decides which samples and values to return. Tool-call logs dropsampleValues, example bullets andvalueMappings.pageContext.attachments{uploadRef, fileName}. That is at most 5 entries of scalars, so no bytes.BulkLoadRunner.start(request) -> RunHandle{status (jobs_job status names), jobId, processUUID, summary}. Launches from the assistant are createdAWAITING_CONFIRMATIONby core.Stand-ins
STAND-IN(core=#1179, removeBy=RC.8): the bulkLoad profile, suggestMapping and validate operations (QRun-IO/qqq#1179) are not in RC.5. Until they ship, the tools tell the user the server does not offer them yet. This usesgateway.isInvocableand makes no call.STAND-IN(core=#1171, removeBy=RC.8): the jobs launch, from the durable-jobs kernel PR.POST /qqq/v1/uploads, built by the jobs-kernel agent. The Next panel's attach button follows once that lands.Shared files touched (additive; the #19/#21 rebase must keep these)
UiApiCall.withFormParam/withHeader, mirroring core'sUiApiRequest.withFormParam.LoopbackUiApiGatewaymultipart bodies, plus forwarding of non-reserved headers.UiApiToolbox.Outcome.of.ContextBuilder.tools,TurnEnginedispatch andAssistantApi.confirmAction.sanitizePageContextattachments.ToolLabels.AssistantQBitConfig.bulkLoadRunnerseam.Tests
BulkLoadToolsTest(6 tests):X-QQQ-AI-Route.forLogstrips samples and values.defaultValue: DRAFT.kof22-admins. Nothing is saved until the user confirms; after Confirm,storeSavedBulkLoadProfileandinsertSharedRecordrun with the right values.up_1.The full
./mvnw verify(all modules, checkstyle, JaCoCo gates, ArchitectureTest, NamingTest, golden and red-team evals) is green.🤖 Generated with Claude Code