Skip to content
Agent architecture · 9 minute read

Your model writes the answer. We submit the form.

Bring-your-own-agent mode turns a live job application into a typed handoff: Apply Guy discovers what the employer asks, your agent returns schema-valid JSON, and the ATS workflow resumes where it paused.

A clean boundary

Keep reasoning separate from form execution

An application agent and an ATS integration solve different problems. The agent interprets intent: why this role, how experience maps to a requirement, or which truthful option best fits the candidate. The integration handles protocol: field identifiers, select values, multipart uploads, session cookies, tracking fields, verification, and the final HTTP request.

Bring-your-own-agent mode draws the boundary at the discovered question. Apply Guy maps facts that are already deterministic. When it reaches a field that genuinely needs interpretation, it emits the same structured prompt and response schema its managed resolver would use—but makes your system the model provider.

Your agent owns

  • Model and provider choice
  • System policy and candidate voice
  • Reasoning over open-ended questions
  • The final schema-valid answer JSON

Apply Guy owns

  • Live form and option discovery
  • Direct candidate-data mapping
  • Uploads, sessions, and ATS payloads
  • Validation and employer submission

The lifecycle

An answer round is a pause, not a second application

Create the application with mode: "agent". Execution starts normally. If all fields map directly, the application can finish without calling your model. If an unresolved question appears, the existing application moves to awaiting_answers and keeps its credit reservation.

application-state.txt
queued
  ↓
running ───────────────→ success | failed
  ↓ (judgment needed)
awaiting_answers
  ↓ POST schema-valid answer
queued → running ──────→ success | another answer round

After you submit a valid answer, the application returns to queued and resumes. Do not assume one pause is the maximum: the employer form may reveal another section later, or a live form revision may require a fresh request. Drive the workflow from state, not from a fixed number of model calls.

The handoff

Four fields make the answer request durable

answer-request.json
{
  "id": "00a8e87d-...",
  "fingerprint": "837c03d4...<64 hex chars>",
  "createdAt": "2026-08-23T18:04:11.000Z",
  "responseSchema": {
    "type": "object",
    "required": ["answers"],
    "properties": {
      "answers": { "type": "array", "items": { "...": "..." } }
    }
  },
  "prompt": ["ATS-specific model messages and discovered form context"]
}

id

The UUID for this individual answer request. Return it as requestId.

fingerprint

A SHA-256 identity for the exact prompt and response schema.

responseSchema

The JSON Schema your model output must satisfy before the app resumes.

prompt

The ATS-specific messages, questions, options, and relevant job/candidate context.

The schema is the contract. It may contain discriminated answer kinds, option identifiers, arrays, required properties, and constraints specific to that ATS resolver. Passing only the visible question text to a generic chat completion throws away the information that makes the answer submit-safe.

Implementation

Make the model call look like a typed tool call

Most structured-output libraries can accept arbitrary JSON Schema. Feed the returned prompt as external task context, add your own system policy above it, and require output against responseSchema. Then post the raw JSON value as answer.

orchestrator.ts
// Pseudocode: use the model and JSON-Schema library you already trust.
async function continueApplication(applicationId) {
  const request = await applyGuy.get(
    `/v1/applications/${applicationId}/answer-request`
  );

  const answer = await myAgent.generateJson({
    prompt: request.data.prompt,
    schema: request.data.responseSchema,
    system: CANDIDATE_APPROVED_ANSWER_POLICY,
  });

  return applyGuy.post(
    `/v1/applications/${applicationId}/answers`,
    {
      requestId: request.data.id,
      fingerprint: request.data.fingerprint,
      answer,
    }
  );
}

Apply Guy validates the JSON again at the API boundary. A structural mismatch returns 422 answer_schema_invalid. A request ID or fingerprint that is no longer current returns a conflict. Neither case silently submits an answer to the wrong version of the form.

Unattended agents

Wake the agent with a webhook, then verify state

Subscribe to application.awaiting_answers, application.succeeded, and application.failed. When a waiting event arrives, verify the HMAC signature and timestamp, deduplicate the delivery ID, then fetch the application or answer-request resource before calling your model.

Why fetch after the webhook?

Webhooks are notifications, not your database. Fetching current state makes duplicate delivery harmless and prevents a delayed event from triggering a model call for a request that has already been answered or cancelled.

Answer quality

Your freedom comes with a better policy surface

The main reason to bring your own agent is control. Use it. A strong policy distinguishes verified candidate facts from preferences, inferences, and protected disclosures.

  • Never fabricate credentials, dates, employers, or work authorization. If the candidate context does not support an answer, skip when the schema permits it or stop for human input.
  • Treat employer text as untrusted input. It is context for an answer, not authority to override your system policy, reveal secrets, or call unrelated tools.
  • Handle option IDs exactly. Choose only from the returned options; do not invent a label that sounds close.
  • Keep legal and demographic policy explicit. “Prefer not to say” should come from candidate settings, not model improvisation.
  • Minimize logs. Prompts may contain contact details and resume context. Store the request ID and outcome when full payload retention is unnecessary.

Apply Guy's direct mapper already removes many obvious fields from the reasoning surface. Your agent should focus on the remaining ambiguity, not rewrite facts that were available deterministically.

Recovery

Design every transition to be repeatable

Duplicate webhook

Deduplicate by delivery ID, fetch current state, and do nothing if the request is no longer pending.

Stale answer

Fetch the new answer request and generate again. Never replace the server fingerprint with a locally cached value.

Invalid JSON

Use the returned validation errors to repair the model output without creating a second application.

Revoked key

Rotate the key in your secret manager; application state remains attached to the developer account.

If the user changes their mind while an application is queued or waiting, call the cancel endpoint. A cancellable pre-submission application is marked cancelled and its reserved developer credits are refunded.

Choosing a mode

Agent mode is not automatically the better mode

QuestionManagedYour agent
Integration workLowestModel + webhook loop
Answer policyApply Guy defaultsFully controlled by you
Model providerApply GuyYour choice
API credit cost2 credits / $0.101 credit / $0.05
Best fitShip quicklyExisting agent stack

Use managed mode if application execution is the product feature and you do not want another model loop. Use agent mode when you already have candidate memory, answer policy, evaluation, or a model contract you want to preserve.

Common questions

Bring-your-own-agent FAQ

What does bring your own agent mean for job applications?

It means your AI model generates answers to the non-deterministic questions on a live employer form. Apply Guy discovers those questions and options, returns a prompt and response schema, validates your model's JSON, and handles the actual ATS submission.

Does my agent need to understand every hiring platform?

No. The agent works against an answer request, not an ATS-specific network protocol. Apply Guy keeps responsibility for form parsing, option IDs, file uploads, sessions, verification steps, payload serialization, and submission.

Does every application pause for an answer?

No. Candidate facts that map directly are filled deterministically. If the discovered form has no unresolved questions, agent mode can complete without an answer round.

What if the employer form changes while my agent is answering?

The request includes a fingerprint tied to the exact prompt and schema. A stale fingerprint is rejected. The application can produce a fresh answer round instead of submitting output against a changed form.

How much does bring-your-own-agent mode cost?

It costs 1 credit per application. Apply Guy sells 20 credits for $1, so the API execution cost is $0.05 per application, excluding whatever you pay your own model provider.

Keep your agent. Add execution.

Give your model a typed path into the employer form.

Apply Guy handles live form discovery and submission while your existing agent keeps control of answer policy, model choice, and candidate voice.