Skip to content

feat(harness): each worker's place, permissions and deadline - #247

Draft
KarthikAvinashFI wants to merge 88 commits into
feat/TH-8875-actor-modelfrom
feat/TH-8886-participants-v2
Draft

KarthikAvinashFI wants to merge 88 commits into
feat/TH-8875-actor-modelfrom
feat/TH-8886-participants-v2

Conversation

@KarthikAvinashFI

@KarthikAvinashFI KarthikAvinashFI commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

1. Why

This is the harness half of "Test a multi-agent application" in the simulation plan (tasks T16 and T17, acceptance tests MA-1 and MA-2). The sandbox's own record becomes the source of how a call's agents relate and which worker did what. Each worker event names its span, the hand-off it ran in, its kind and whether that relationship was observed. Where the framework gives no evidence, the record says unknown; nothing is inferred from names or event order.

The PR stacks on Actors (#246, feat/TH-8875-actor-model). Worker permissions and deadlines go through the actors' tool proxy: the same permitted() check and refusal path, the same action log, and the trial state file that already carries faults. The platform half is future-agi/future-agi#3754.

Plan items and where they are

Plan item In this PR
T16: the app keeps its orchestration, pinned as one target Workers are declared in the contract; a blank or repeated worker id is returned to authoring. The returns reference app is a coordinator with two workers
T16: prove world bindings A worker whose tools run through code of their own (binding: separate, address_env) gets its own tool-proxy channel per world. The token is never logged or forwarded
T17: delegation and return events retained Sealed rows carry span_id, parent_span_id, kind (delegated, transferred, tool_call), relationship and ended_at. Real OpenAI Agents SDK 0.24 and ADK 2.13 span shapes are covered
T17: tool actions carry participant identity A world call through a worker channel is logged with that worker. The allow-list (contract allowed_tools) is enforced at the proxy, and an out-of-permission call is refused (not_permitted) into the action log
T17: refusal, permission failure, timed-out worker A worker's attempts and refusals (with code and reason) are their own entry in the trial's state changes. A scenario's worker_budget_seconds becomes a deadline the proxy enforces (deadline_passed)
T17: worker-specific checks A sub-goal's subject: worker:<id> grades only that worker's calls. Worker verdicts are reported beside the task verdict and never decide it. With no recorded calls, or past its deadline, a worker's check is inconclusive
MA-1, MA-2 No return is written unless the framework recorded the worker ending; missing evidence reads unknown
Split the observation module; narrow the support claim (review on #226) Reading is in simulation_worker_events.py; section 3 lists the observed shapes

2. What changed

File Change
simulation_worker_events.py (new), simulation_mark_bootstrap.py The span-to-event reader, moved out of the startup hook. Framework transfers are held until the named or next agent runs
tool_trace_proxy.py Worker channels, allow-lists, deadlines, worker id on action-log rows, call names from actions.json
bundle_author_v2.py Seals permissions.json and actions.json, writes worker channel addresses, seeds the full action log when workers are limited
process_runtime.py Renders a per-world channel token for each separate worker
call_runner.py, chat_call_runner.py, chat_readiness.py One evidence-seam reader for voice and chat. Trial state carries the worker deadline. Worker entries in state changes
hosted_scheduler.py, catalogue.py, scenario_source.py, outbound.py, hosted_entrypoint.py Sub-goal subject; worker lanes; verdicts beside the task
contract.py, tools.py, scenario.py, scenario_tools.py, skills allowed_tools, binding, address_env, worker_budget_seconds, subject; writer and builder guidance
examples/harness/returns_reference Ledger over HTTP, a client per worker, a compose file

Update impact: no change to the platform's storage. Sealed rows and receipts gain optional fields, which readers that do not know them ignore.

3. Observed shapes

Framework Shape What is observed
OpenAI Agents SDK Agents as tools Hand-off at the top level of the run, its return, and each worker tool under its own hand-off
OpenAI Agents SDK Handoffs Control pass the framework records; the receiving agent's calls are unknown until it records who handed control
Google ADK AgentTool Hand-off and return; each worker tool under its own hand-off
Google ADK transfer_to_agent Control pass to the agent that runs next in the same workflow (named in the call when recorded); that agent's hand-offs and calls under it; no return
LangGraph Subgraphs, sub-agents through a tool, background jobs Hand-off with its return when the job ends; a job still running at call end keeps its hand-off with no return
AutoGen, Claude Agent SDK, CrewAI Agents and tool calls Each tool call with its agent; CrewAI agent runs as hand-offs with their tasks

Reported as unobserved or unknown, never filled in:

  • A framework whose instrumentor is not installed, or an app that turns its framework's tracing off.
  • A transfer-named call whose agent never runs: an ordinary tool call.
  • An agent run nested in another with no tool call or transfer between them: unknown.
  • A tool call with no agent above it: unknown.
  • Non-Python processes and apps run through Compose: not observed.

4. What we tested on

Case Ran on Quality check Result
OpenAI Agents, agents as tools Real SDK 0.24 + traceAI with a scripted model; returns app live Coordinator's hand-offs at the top, observed; each worker tool under its own hand-off Pass
ADK transfer and nested AgentTool, two turns Real ADK 2.13 with a scripted model; travel concierge live Control pass to inspiration_agent with no recorded arguments; nested hand-off under it; parallel hand-offs in turn 2 Pass
Separate worker channels New returns build on the local stack, production sandbox specs Understand stage declared both workers as separate with their address and allow-list; each world call names its worker Pass, 4 scenarios
Refusal and duplicate refund Same build Outside-window refund refused; duplicate reports the existing refund, no second refund Pass
Permission failure New returns build, mark-order scenario Order-lookup's update_order_status refused at the proxy (not permitted for order_lookup), logged with the worker; the worker's own check passed Pass
Worker-specific verdict Same build, refund scenario worker:refund sub-goal graded on the Refund worker's calls only, reported beside the task Pass
Timed-out worker Same build, worker_budget_seconds: 3 Every Order-lookup call refused deadline_passed before the world; Refund never ran, its check inconclusive Pass
AutoGen Handoffs app Each tool call carries its agent Pass
Rules Proxy, scheduler and call-runner unit tests, each seen failing with its change reverted not_permitted and deadline_passed refused before the world and logged with the worker, and kept on the sealed trace row; a worker's check graded on its lane only Pass

Not tested

Case Reason
Hand-offs of an app run through Compose Not observed; the returns build runs through Compose, so its steps read unknown
LangGraph live One environment fails its world seed (source-model fingerprint check in world_ir.py), the other fails at the call on its response path; a fresh build is needed
CrewAI live The call errored evidence_missing: no tool calls captured by the CrewAI observer for that scenario

5. Tests

tests/harness/: span, proxy, call-runner, scheduler, contract, scenario-source, chat-readiness and actor-runtime suites pass. Nine tests in the full suite fail on this branch and on its base the same way (test_dispatch_ack ×4, test_proof_failed_attempts ×4, test_grade cap).

PYTHONPATH=src pytest -q tests/harness/test_framework_tool_spans.py tests/harness/test_tool_trace_proxy.py tests/harness/test_call_runner.py tests/harness/test_hosted_scheduler.py tests/harness/test_contract.py

Linear

References: TH-8884, TH-8886, TH-8887, TH-8888

…t/TH-8886-declared-workers

# Conflicts:
#	src/fi/alk/harness/call_runner.py
#	src/fi/alk/harness/run/simulation.py
#	src/fi/alk/harness/world/runtime.py
…and join a job that starts before its launch returns
…de it, as its own span is named only when it ends
… each tracer read, and a job without an id is never taken for an ended one
…hapes, including a transfer that outlives its agent
…permissions-v2

# Conflicts:
#	src/fi/alk/harness/call_runner.py
#	src/fi/alk/harness/hosted_scheduler.py
@KarthikAvinashFI
KarthikAvinashFI changed the base branch from feat/TH-8886-declared-workers to feat/TH-8875-actor-model November 5, 2026 14:03
@KarthikAvinashFI KarthikAvinashFI changed the title fix(harness): record each worker event's place among the agents from the spans alone feat(harness): record each worker's place, permissions and deadline from the app and the world Nov 5, 2026
@KarthikAvinashFI KarthikAvinashFI changed the title feat(harness): record each worker's place, permissions and deadline from the app and the world feat(harness): each worker's place, permissions and deadline Nov 5, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant