Skip to content

Testing plan: mount tiny-harness agents onto an existing FastAPI or Starlette app ​

Derived from requirements.md and design.md, before tasks.md. Authored at test-planning, completed at verification.

This file is executable content. It names commands an agent will run; review it like code. No credential is needed. Every test runs offline over a scripted model (tiny_harness.harness.models.scripted) and a local Temporal dev server (start_local, as since PR #28).

The work is proved in six layers:

LayerWhat it covers
UnitThe pure pieces: public_url, Claims, TemporalPool, RouteOptions, the resolver, ownership with a fake client, SecretRegistry, AgentConfig
IntegrationHarnesses mounted in real host apps, served by a real uvicorn server on an ephemeral loopback port, so agent-to-agent calls and pushes travel real HTTP
AcceptanceThe issue author's test: five agents in one app, delegating to each other
SecurityOne negative test per abuse case in design.md § Security design
ContractThe settings schema, the public-API snapshots and the unchanged agent card
Staticruff, pyright strict and markdownlint, as CI

Test matrix ​

#TypeApplies?Scope / what it provesWhere it runs
T1Unityespublic_url (every row of the design's table, normalization, the mismatch error); Claims (each clash, release, resolved store paths, thread safety); TemporalPool reference counts and keys (no API key in the key); RouteOptions toggles (which routes and middleware exist); HarnessContextBuilder with and without a resolver, resolver raising; strict sender_of; TemporalTaskStore and PushReceiver ownership over a fake client (owned, foreign, describe failing, LRU hit); SecretRegistry versioning in Redactor; schedule_id; AgentConfig and effective_task_queue defaults; the ListTasks context_id escaping; Harness lifecycle errors (second start, start after stop, client before start)uv run pytest tests/unit
T2Integration (scenario)yesOver the local Temporal dev server, Gherkin-documented: one harness mounted under a prefix in a host Starlette app; the 503 window before start; the wrong-mount refusal; the card's prefixed URLs; the top-level HarnessApp lifespan; the FastAPI router (lifespan merge, OpenAPI tags and paths, 503 and limits dependencies, include-prefix refusal); the push route off ⇒ follow; harnesses_lifespan starting and stopping in order; a part-way start failure undoing what ran; a client passed in and not closed; the agent id on log linesuv run pytest tests/integration/mount
T3Acceptance (scenario)yesexamples/multi_agent/: five configurations, one Starlette app (and its FastAPI variant), one shared embedded dev server. Agent a delegates to b over HTTP and gets the result; b parks and its push reaches a's prefixed route; b delegates on to d. All five cards resolve at their prefixesuv run pytest tests/integration/mount/test_multi_agent.py
T4Security / abuse caseyesAbuse cases 1–6 of design.md: header ignored with a resolver, and a message's self-claim refused in strict mode; every task operation of agent b refused on agent a (GetTask, ListTasks, CancelTask, SubscribeToTask, four push-config operations, SendMessage with b's id), with b's task unchanged; b's stand-in pushed at a's route → 401, not woken; shared namespace and queue → HarnessConflictError, no worker polled; a request before start → 503, no Temporal call; agent b's key in agent a's log line → maskeduv run pytest tests/security/test_multi_agent_isolation.py tests/security/test_participant_resolver.py
T5Contractyessettings.schema.json gains [agent] and an optional task_queue. The public-API snapshots of tiny_harness.service, tiny_harness.config and tiny_harness.errors gain Harness, harnesses_lifespan, RouteOptions, AgentConfig and the new errors, and nothing else changes. agent-card.json is unchanged (R7.3). A new snapshot covers tiny_harness.fastapiuv run pytest tests/contract
T6Back compatibility (regression)yesR7: the whole existing suite passes unmodified on the rebuilt running_harness and serve, except for tests that import the removed runtime.AGENT_ID. Each such edit is listed in the evidence with its reason. A configuration file from examples/demo/ loads with id and queue tiny-harness, and the default heartbeat schedule id is still tiny-harness-heartbeatuv run pytest tests/unit tests/integration tests/contract tests/security tests/ui
T7PackagingyesThe built wheel installs without FastAPI, and import tiny_harness.fastapi raises the ImportError naming the extra (R3.4). With tiny_harness[fastapi], the router imports. Extends tests/integration/test_package.pyuv run pytest tests/integration/test_package.py
T8End-to-endn/a: the e2e suite runs the demo against Temporal Cloud and OpenAI with one agent. T3 runs the whole multi-agent path over real HTTP and a real Temporal server; a live model adds nothing to what is proved here.
T9UI / visualn/a: no renderer or TUI change (design § UI/UX: N/A).
T10Snapshotn/a beyond T5: no rendered output changes.
T11Performance / loadyes (narrow)NFR startup and overhead: five harnesses sharing a pool open one Temporal connection (counted by the pool and asserted in T3); a known task id's ownership check makes no second describe (LRU, asserted in T1). No timing benchmark: the 503 gate is one boolean read(asserted inside T1, T3)
T12Accessibilityn/a: no user interface.
T13Migration / upgradeyes (narrow)Covered by T6: an existing configuration without [agent] or with an explicit task_queue keeps today's id, queue and schedule id. No store schema or workflow signature changes, so there is no data migration and no replay risk(inside T6)
T14Manual exploratoryyesRun examples/multi_agent/ by hand per its README with tiny-harness tui --url http://127.0.0.1:8090/agents/a and a scripted or real model; open the FastAPI variant's /docs and check the five tagged groups. Confirms that the README steps work as writtenmanual, per examples/multi_agent/README.md
T15Docs buildyesThe new and changed pages (docs/sdk/mount.md, docs/configuration/agent.md, the deploy guide, capability docs) build and pass markdownlintcd docs && bun run docs:build
T16Staticyesruff, pyright strict (including tiny_harness/fastapi.py with FastAPI installed in the dev group), markdownlint over all files, as CIuv run pre-commit run --all-files

Scenarios & requirement trace ​

RowRequirement(s)Scenario / case
T1R2.4public_url table and mismatch ConfigError
T1R5.3, R5.8Claims refuses a duplicate id, namespace+queue, store path
T1R5.2, NFRTemporalPool shares one client per key; the key holds no API key
T1R4.1–4.3resolver used, header ignored, None ⇒ no participant, raise ⇒ error
T1R4.6, R7.2AgentConfig defaults; queue defaults to the agent id
T2R1.1–1.5Scenario: A harness starts from a host lifespan and opens no port
T2R1.3Scenario: A Temporal client passed in is used and not closed
T2R2.1, R2.3Scenario: An agent mounted under a prefix serves its card with prefixed URLs
T2R2.2Scenario: A mounted agent answers 503 until its harness has started
T2R2.4, design § mount checkScenario: An agent mounted at the wrong path refuses every request
T2R2.5, R7.1Scenario: A top-level harness app starts itself from the server lifespan
T2R3.1–3.3Scenario: A FastAPI app includes an agent router and lists its routes in OpenAPI
T2R3.2Scenario: A router included under another prefix refuses every request
T2R4.4Scenario: Limits and CORS can each be turned off
T2R4.5Scenario: With the push route off a remote sub-task is followed
T2R5.1Scenario: Several harnesses start and stop in order from one lifespan
T2NFR observabilityScenario: Each log line names the agent that wrote it
T3R5.1–5.7, R6.1–6.4, NFR startupScenario: five agents in one app delegate to each other
T3R6.2Scenario: A parked sub-task on agent b pushes to agent a's prefixed route
T3R3, R6Scenario: five agents in one FastAPI app delegate to each other
T4abuse 1Scenario: With a resolver a spoofed participant header is ignored
T4abuse 2, R5.4Scenario: Agent a treats every task of agent b as unknown
T4abuse 3Scenario: A push for agent b's stand-in at agent a's route is refused
T4abuse 4, R5.3Scenario: A second harness on the same namespace and queue does not start
T4abuse 5Scenario: A request before start never reaches Temporal
T4abuse 6Scenario: One agent's log masks another agent's key
T5R3.4, R4.6, R7.3snapshots: schema, public API, unchanged card
T6R7.1–7.3existing suite unmodified
T7R3.4wheel without the extra; with the extra
T14R8.1README walkthrough
T15R8.1–8.4docs build

Verification environment ​

  • Repositories: this repository only, branch loop/issue-36.
  • Services: the Temporal dev server, started by the tests through start_local (downloaded once into the uv cache). Nothing else to bring up.
  • HTTP: T2 and T3 serve the host app with uvicorn on 127.0.0.1 and an ephemeral port chosen by the OS. Agents reach each other and push to each other over that real loopback socket. httpx.ASGITransport is not used where a stream must stay open, because it buffers server-sent events.
  • Fixtures & data: scripted models (tiny_harness.harness.models.scripted) per agent; temporary directories for each agent's store and the shared embedded Temporal database; the five examples/multi_agent/*.toml files, with base_url and paths rewritten for the test's port and temporary directory.
  • Credentials: none. OPENAI_API_KEY is only for the optional manual run in T14, by reference, and is never written down.
  • Bring-up: uv sync --locked (the dev group includes FastAPI).
  • Tear-down: none. pytest's tmp_path and the tests' context managers stop every server and remove every file.
  • Temp space: if /tmp is full, set TMPDIR to a directory under ~/.cache before running.
  • If bring-up fails (the dev server cannot download or start): record it under Verification results, leave T2–T4 and T6 unticked, and escalate on PR #37.

Evidence plan ​

RowEvidencePath under evidence/
T1test summary (counts, duration)unit.md
T2, T3the-loop scenarios --root "$PWD" --format markdown table for the new scenarios, plus run outputintegration.md
T3the run log of the five-agent scenario, showing a's delegation to b, b's push to /agents/a/a2a/push/…, and one pool connectionacceptance.md
T4run output, one line per abuse casesecurity.md
T5snapshot diff (schema, public API) and the unchanged cardcontract.md
T6full-suite summary, and the list of edited existing tests with reasonsregression.md
T7package test outputpackaging.md
T14the manual walkthrough steps and outcomes; a redacted screenshot of the FastAPI /docs page with the five groupsmanual.md, manual/openapi-docs.png
T15, T16build and pre-commit outputstatic.md

All output is redacted before it is committed. It is loopback-only, but it still gets checked for tokens and home paths.

Verification activities ​

  • [x] T1 — uv run pytest tests/unit
  • [x] T2 — uv run pytest tests/integration/mount
  • [x] T3 — uv run pytest tests/integration/mount/test_multi_agent.py
  • [x] T4 — uv run pytest tests/security/test_multi_agent_isolation.py tests/security/test_participant_resolver.py
  • [x] T5 — uv run pytest tests/contract
  • [x] T6 — uv run pytest tests/unit tests/integration tests/contract tests/security tests/ui
  • [x] T7 — uv run pytest tests/integration/test_package.py
  • [x] T14 — manual walkthrough of examples/multi_agent/README.md, and the FastAPI /docs check
  • [x] T15 — cd docs && bun run docs:build
  • [x] T16 — uv run pre-commit run --all-files

Verification results ​

Executed on 2026-10-13 against branch loop/issue-36. Every planned activity ran, and all pass.

ActivityCommand / procedureOutcomeEvidence
T1 Unituv run pytest tests/unit632 passed. A mutation check confirms the ownership test fails without the queue checkunit.md
T2 Integrationuv run pytest tests/integration/mount14 passed (8 Starlette, 3 FastAPI, 3 five-agent)integration.md
T3 Acceptanceuv run pytest tests/integration/mount/test_multi_agent.py3 passed: five agents delegating over HTTP in Starlette and FastAPI, a push to a's prefixed route, one pool connectionacceptance.md
T4 Securityuv run pytest tests/security/test_multi_agent_isolation.py tests/security/test_participant_resolver.py6 passed, one per abuse case 1–6security.md
T5 Contractuv run pytest tests/contract37 passed. Snapshots changed on purpose, agent card unchangedcontract.md
T6 Regressionuv run pytest tests/unit tests/integration tests/contract tests/security tests/ui902 passed (after fixing two bugs in the new tests, see evidence). One existing fixture moved its monkeypatch to the new seamregression.md
T7 Packaginguv run pytest tests/integration/test_package.py2 passed. Without the extra, the import names tiny_harness[fastapi]packaging.md
T11 Performance (narrow)Asserted inside T1 (LRU: a known id is described once) and T3 (open_connections() == 1)Passunit.md, acceptance.md
T13 Migration (narrow)Inside T6 and T1 (test_the_demo_configuration_loads_unchanged, test_no_agent_section_keeps_todays_id_and_queue, default schedule id)Passregression.md
T14 Manualexamples/multi_agent FastAPI variant with a real modelPass, with two findings: the README did not say to unset TEMPORAL_API_KEY (fixed), and without naming the agent the model does not delegate, because remote agents are not in the system prompt (issue-34's scope)manual.md, screenshot
T15 Docs buildcd docs && bun run docs:buildBuild completestatic.md
T16 Staticuv run pre-commit run --all-filesAll hooks passstatic.md

Not executed: none.

Review comments ​

Appended by the-loop's record-feedback hook when a human gate approves with comments (issue-109). Append-only and attributed: an approval never silently discards a reviewer's suggestions, and the feedback travels with the document it concerns rather than living in a side-channel tracker.

2026-10-13 — approved ​

@MadaraUchiha-314 wrote:

approved