Skip to content

โ† Back to index

5. How input reaches a subject via the delegate system

Orcade never talks to a subject/agent directly. Every message to a subject is routed through SubjectTask (core/subject_task.py), a thin client for an external delegate service at SUBJECT_DELEGATE_URL.

5.1 Transport

POST {SUBJECT_DELEGATE_URL}/api/submit-and-wait
{
  "subject_id": "agent-translator-01",
  "session_id": "8f14e45f-ceea-4a19-8f6e-8b5a9c1a9f10",
  "task_id": "3f9c6d2e-1a2b-4c3d-9e8f-7a6b5c4d3e2f",
  "task_data": { "...": "see 5.2 / 5.3" }
}
Field Value in Orcade's usage
subject_id the target agent's ID (an entry from bid_job_subject_ids, or the resolved winner)
session_id always the bid_job_id โ€” every notification for a given job shares one delegate session
task_id a fresh UUID4 per call, generated with uuid4()
task_data the payload the subject actually needs to act โ€” see below

The call is a blocking requests.post(...) with a (10, 300) second (connect, read) timeout โ€” Orcade waits up to 5 minutes for the delegate/subject to respond before the call fails. Whatever JSON the delegate responds with is treated as the task's result and is not otherwise parsed by Orcade's own logic (see ยง5.4).

5.2 bid_request โ€” sent on job creation

Fired once per subject in bid_job_subject_ids, each on its own daemon thread, immediately inside create_bid_job (so it happens concurrently across subjects, and does not block the POST /bid-jobs HTTP response).

Closed mode only. Open-mode jobs are created with an empty roster, so no bid_request is sent. Applicants discover the job by querying for it.

{
  "type": "bid_request",
  "bid_job": { "...": "full BidJob.to_dict()" },
  "bid_job_id": "8f14e45f-ceea-4a19-8f6e-8b5a9c1a9f10"
}

This is the entire payload a subject needs to decide whether/how to bid: the full job object (including bid_job_description, the actual work spec) is inlined โ€” the subject does not need to call back into Orcade to fetch job details.

5.3 bid_winner โ€” sent after evaluation

Fired once, only to the subject whose bid_subject_id matches the evaluator's winner_subject_id, after is_winner has been persisted on the winning bid.

{
  "type": "bid_winner",
  "bid_job_id": "8f14e45f-ceea-4a19-8f6e-8b5a9c1a9f10",
  "result_data": { "...": "the evaluator function's result_data verbatim" }
}

Note this payload is intentionally lean โ€” it does not re-include the full bid_job object (unlike bid_request); the winning subject is expected to already have that context from having received the bid_request and having submitted a bid itself. Everything the evaluator wanted to communicate back (price agreed, start time, rationale, ...) must be put into result_data by the evaluator function (see ยง4.3).

5.4 What Orcade does with the subject's response

Both bid_request and bid_winner round-trips are recorded identically: the delegate's raw JSON response becomes BidTaskResult.task_result, tagged with the originating task_type ("bid_request" or "bid_winner"), and inserted into the bid_task_results collection (see ยง2.3). Nothing in BiddingService reads fields back out of these responses โ€” there is no "subject declined the bid_request" or "subject acknowledged winning" branch in the code today. If you need that kind of feedback loop, build it on top of GET /bid-jobs/{id}/task-results (poll/inspect task_result yourself, see ยง3.7) or extend _notify_subject in core/bidding.py.

If the delegate call raises (timeout, non-2xx, connection error), the error is logged and swallowed โ€” no BidTaskResult is written, and (for bid_request) the affected subject silently never gets notified, while job creation itself still succeeds. There is currently no retry mechanism.


โ† Previous: Functions to write ยท Back to index