Skip to main content

EOP Workflow

Overview

The K12 EOP workflow (formerly referred to as the "Annex" workflow) is an asynchronous job-based pipeline that accepts an emergency operations plan editing request, runs the K12 safety agent to produce edits, and persists telemetry & results for polling and retrieval.

Note: Tool IDs still contain "annex" for historical reasons (e.g., k12-annex-editing-kickoff). Do not change tool IDs in code — documentation uses the accurate EOP name.

This document expands on the workflow map and maps each workflow step to the concrete tool(s) that perform it.

Public tool endpoints (APIM)

All EOP editing requests go through the async toolchain exposed via the K12 APIM host.

  • Base URL (K12 Azure Dev APIM): https://dev-k12-aitools-t27p-apim.azure-api.net
  • Endpoint template: POST /ai/api/tools/{toolId}/execute
  • Payload shape: { "data": { ... } }
  • Required headers (APIM):
    • Authorization: Bearer {access_token}
    • Ocp-Apim-Subscription-Key: {subscription_key}

Note: Examples on this page use APIM subscription keys for quick curl snippets; for tenant-hosted automation and production workflows, prefer OAuth bearer tokens (client credentials) issued by the Azure Entra tenant. - Content-Type: application/json

When calling a local dev server without APIM/auth, auth headers may be omitted.

1) Kickoff

  • Tool ID: k12-annex-editing-kickoff
  • Endpoint: POST /ai/api/tools/k12-annex-editing-kickoff/execute
  • Purpose: Enqueues an EOP editing job and returns tracking identifiers.

Key request fields (inside data):

FieldRequiredNotes
emergencyOperationPlanId⚠️Plan identifier to update. Organization context still requires emergencyOperationPlan.organizationId.
emergencyOperationPlan⚠️Frontend-provided EOP object; id, emergencyOperationPlanId, planId, or eopId is normalized as the plan identifier. organizationId is used directly for district/organization details.
emergencyOperationPlanConfigurationId⚠️Configuration-stage identifier. Organization context still requires emergencyOperationPlanConfiguration.organizationId; mutually exclusive with plan and Master Copy identifiers.
emergencyOperationPlanConfiguration⚠️Frontend-provided configuration object; ID aliases are normalized as the configuration identifier. organizationId is used directly for district/organization details.
eopMasterCopyId⚠️Master Copy identifier for pre-EOP template form editing. Organization context still requires emergencyOperationPlanMasterCopy.organizationId; supported by the legacy and scenario-specific EOP kickoff tools.
emergencyOperationPlanMasterCopy⚠️Frontend-provided Master Copy object. Saved objects may provide an ID alias; organizationId is used directly for district/organization details.
userIdRequesting user (GUID or email).
roleIdOptional role/persona identifier retained for traceability.
userTagIdsAlias for selected role/tag IDs; may be an empty array while frontend tag filling is pending.
roles, userTags, generalUserTagsFrontend-provided role/tag objects or names. These override backend-resolved role tags for prompt context when present.
form.field.contentHTML/rich-text. Use "" when doing prompt-only edits.
promptAdditional user instruction.
sessionIdCorrelation id; generated if omitted.
requestIdDeterministic job id; generated if omitted.
waitForResulttrue blocks until finished; default is async.

Exactly one identity source must be provided: emergencyOperationPlanId/emergencyOperationPlan, emergencyOperationPlanConfigurationId/emergencyOperationPlanConfiguration, or eopMasterCopyId/emergencyOperationPlanMasterCopy. District/organization details are resolved only from the explicit organizationId on the selected request object; the workflow no longer discovers organization IDs indirectly from EOP, configuration, or Master Copy IDs.

Example (cURL — kickoff, async):

curl -X POST "$BASE_URL/ai/api/tools/k12-annex-editing-kickoff/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"emergencyOperationPlan": {"id": "plan-sample", "organizationId": "org-sample"}, "userId": "principal@example.edu", "roleId": "safety-coordinator", "form": {"field": {"content": "<p>...</p>"}}, "prompt": "Shorten to two bullets"}}'

Example response (accepted):

{
"requestId": "9fd2055d-42ab-4c29-bf0d-51bd8f7db3ec",
"sessionId": "0e1a5e3f-3a3c-4d1f-bbc6-0e76b1c4a8c7",
"status": "accepted"
}

2) Status polling

  • Tool ID: k12-annex-editing-status
  • Endpoint: POST /ai/api/tools/k12-annex-editing-status/execute
  • Request shape: { "data": { "requestIds": ["..."] } }

Example (cURL — status poll):

curl -X POST "$BASE_URL/ai/api/tools/k12-annex-editing-status/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestIds": ["$REQUEST_ID"]}}'

Example response:

[
{
"requestId": "9fd2055d-42ab-4c29-bf0d-51bd8f7db3ec",
"sessionId": "0e1a5e3f-3a3c-4d1f-bbc6-0e76b1c4a8c7",
"status": "processing"
}
]

3) Result retrieval

  • Tool ID: k12-annex-editing-result
  • Endpoint: POST /ai/api/tools/k12-annex-editing-result/execute

Example (cURL — result fetch):

curl -X POST "$BASE_URL/ai/api/tools/k12-annex-editing-result/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "$REQUEST_ID"}}'

Example response (success):

{
"requestId": "9fd2055d-42ab-4c29-bf0d-51bd8f7db3ec",
"sessionId": "0e1a5e3f-3a3c-4d1f-bbc6-0e76b1c4a8c7",
"status": "success",
"message": "<p>Edited annex text ...</p>",
"isEdit": true
}

4) Cancel job (optional)

  • Tool ID: k12-cancel-task-request
  • Endpoint: POST /ai/api/tools/k12-cancel-task-request/execute

Example (cURL — cancel):

curl -X POST "$BASE_URL/ai/api/tools/k12-cancel-task-request/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUBSCRIPTION_KEY" \
-H "Content-Type: application/json" \
-d '{"data": {"requestId": "$REQUEST_ID"}}'

End-to-end steps (expanded)

  1. Kickoff / normalize request — tool: k12-insert-task-request-log (insert-task-request-tool.ts)

    • Normalizes sessionId/requestId (generate deterministic UUIDs when missing).
    • Persists raw payload into K12SAFETY.log_EOP_TaskRequests.
    • Public kickoff/status/result/cancel contracts are documented above in Public tool endpoints (APIM).
  2. Create initial monitoring entry — tool: k12-insert-task-in-monitoring-table (insert-task-monitoring-tool.ts)

    • Writes a processing row into K12SAFETY.log_EOP_TaskStatus and returns statusHistory.
  3. Invoke the K12 Safety agent — step id: k12_invoke_agent

    • Agent fetched via getK12SafetyAgent() uses Core.AgentProfiles for system prompt overrides and may apply LLM overrides.
  • The agent runs using the Azure AI Inference provider and returns an agentResponse (text + raw + parsed payload).
  1. Persist agent response — tool: k12-update-task-request-response (update-task-request-response-tool.ts)

    • Writes ResponseStatus, ResponseMessage, ResponseIsEdit, and ResponsePayloadJson back to log_EOP_TaskRequests.
  2. Finalize status — tool: k12-update-task-status (update-task-status-tool.ts)

    • Marks the monitoring entry success (or failed/error on exceptions) in log_EOP_TaskStatus.

Tool-level contracts

Each tool exposes a Zod inputSchema/outputSchema. You can import and validate against them in tests or when composing your own orchestrators. Example:

import { insertTaskRequestTool } from 'packages/domain-k12/src/insert-task-request-tool.ts';

// access the tool's Zod schemas
const { inputSchema, outputSchema } = insertTaskRequestTool;

// validate raw input before calling the execute helper (tests or scripts)
const parsed = inputSchema.parse({ taskName: 'K12 EOP Workflow', requestId: 'req-123', sessionId: 's-123', userId: 'u1', roleId: 'r1', form: { field: { content: '<p>...</p>' } } });

// tools provide `execute` to run the same code paths the API uses
const row = await insertTaskRequestTool.execute(parsed);

Fire-and-forget vs synchronous runs

  • The kickoff tool supports both an async (default: accepted) and blocking mode (waitForResult: true).
  • In async mode the tool enqueues the job (writes log_Jobs) and returns tracking ids immediately. Use k12-annex-editing-status or k12-annex-editing-result to poll for completion.
  • In sync mode the tool waits for the workflow to finish and returns full success payload or failed information.

Runner API & schema (developer reference)

The kickoff runner exposes a reusable programmatic helper you can call from scripts or tests:

runAnnexWorkflowKickoff({ input, mastra? }) → returns either the accepted envelope or the synchronous success envelope.

Example TypeScript import:

import { runAnnexWorkflowKickoff } from 'packages/domain-k12/src/annex-editing-workflow-runner.ts';
import type { KickoffInput } from 'packages/domain-k12/src/annex-editing-kickoff-schemas.ts';

const res = await runAnnexWorkflowKickoff({ input: myKickoffPayload, mastra: runtime });

Key details:

  • The runner verifies a registered workflow ID (K12-annex-workflow) is available; if missing it throws an informative error listing available workflows.
  • It creates a Mastra createRun() run and stores workflowRunId in SQL rows for correlating runtime state and log rows.
  • Duplicate requestId values are treated as idempotent: the runner returns a 400-like error (Duplicate requestId) when a prior job exists for that requestId.

The KickoffInput Zod schema enforces fields such as one identity (emergencyOperationPlan or emergencyOperationPlanConfiguration, including explicit organizationId for organization context), userId, roleId, and a form object (see annex-editing-kickoff-schemas.ts). Here is a minimal example payload:

{
"emergencyOperationPlan": {"id": "plan-123", "organizationId": "org-123"},
"userId": "principal@example.edu",
"roleId": "safety-coordinator",
"form": { "field": { "content": "<p>Update the evacuation steps...</p>" } },
"waitForResult": false
}

Useful SQL queries for troubleshooting

  • Check job rows:
SELECT request_id, job_type, status, error, created_at, updated_at
FROM K12SAFETY.log_Jobs
WHERE request_id = '...';
  • Inspect request log entries (most recent):
SELECT TOP (5) ID, TaskName, RequestID, SessionID, CreatedAt, ResponseStatus
FROM K12SAFETY.log_EOP_TaskRequests
WHERE RequestID = '...'
ORDER BY CreatedAt DESC;
  • Get the status timeline:
SELECT TOP (10) ID, TaskName, RequestID, Status, Message, CreatedAt
FROM K12SAFETY.log_EOP_TaskStatus
WHERE RequestID = '...'
ORDER BY CreatedAt DESC;

Operational & troubleshooting tips

  • If a run hangs, check both K12SAFETY.log_Jobs (job runner state) and log_EOP_TaskStatus (processing timeline). The workflow step that executed last is the best hint of the problem.
  • Use k12-cancel-task-request to ask the system to mark rows cancelled and stop in-progress runs (it updates DB rows; Mastra-level cancellation is best-effort unless K12_ENABLE_WORKFLOW_CANCELLATION is enabled).
  • For local debugging, import the EOP runner runAnnexWorkflowKickoff from annex-editing-workflow-runner.ts to run the logic synchronously in tests.

Files to inspect

  • Workflow: packages/domain-k12/src/k12-annex.workflow.ts
  • Tools: packages/domain-k12/src/*.ts
  • Agent: packages/domain-k12/src/agents/k12-safety-agent.ts
  • Tests: packages/domain-k12/tests/tools/, tests/integration/k12/, tests/shared/integration/workflows/k12-annex-schemas.test.ts