FEATURE SPEC¶
Questions or comments?
Post them as a comment on the tracking issue -- requires a GitHub account with access to this repo.
Transfer Agent Architecture — External A2A Agent Session Library¶
Novaworks AI | nova-platform-sn-app + nova-agent-utils
Parent Feature Set: ../concept.md.
Major Change History¶
| Version | Date | Author / Notes |
|---|---|---|
| 1 | 2026-08-11 | Eswar Vandanapu — Initial draft, scoped as Super Agent conversation hand-off infrastructure (see prior spec content, superseded by v2). |
| 2 | 2026-08-12 | Vineela — Complete rewrite after PM discussion: this Feature is not Super Agent-specific conversation hand-off. It's a general-purpose library any Nova Python service (Super Agent, another functional agent, or a UI's backend) can call to open a streamed session with an externally-registered A2A agent. Renamed from transfer-agents to transfer-agent-architecture to reflect that this is architecture/infrastructure, not the conversation-transfer use case specifically. Does not involve nova-superagent-agent as a repo — that repo is a future consumer of this library, not part of this Feature. |
| 3 | 2026-08-12 | Vineela — Corrected x_novaw_platform_extension_point's scope: this Feature creates the table only, it does not seed it from concept.md. Other Nova SN apps populate it with their own rows as each extension point is actually built. |
| 4 | 2026-08-12 | Vineela — Resolved Open Question on library location: this Feature gets its own new subpackage in nova-agent-utils, not an addition to an existing one. |
| 5 | 2026-08-12 | Vineela — Resolved Open Question on Agent Card storage shape: x_novaw_platform_external_agent stores the Agent Card's full content directly, not a URL fetched live. |
| 6 | 2026-08-12 | Vineela — Confirmed the security scheme is read via runtime Agent Card parsing (not a separately stored field) — the library already has to parse the card for the endpoint URL, so the scheme comes from the same parse. Also required that completed/failed/input-required/auth-required be surfaced to the caller as four distinguishable outcomes, not lumped into one generic error. |
| 7 | 2026-08-12 | Eswar Vandanapu — PM requires this version to support input-required: the agent can pause mid-task to ask for more information and the session must be resumable, not a hard failure. Resolved as close-and-resume-later (no long-held connection) keyed by the A2A task/context id, with multiple pause/resume rounds allowed per task. Reverses v1's "single-shot only" scoping for this one case; auth-required remains a true terminal outcome, unaffected by this change. |
| 8 | 2026-08-12 | Vineela — Resolved the last Open Question: durable credential storage is a new Postgres table with KMS envelope encryption, not ServiceNow itself. Confirmed ServiceNow's native Credential Store has no external-facing read path for a non-ServiceNow caller (only a discouraged custom-proxy workaround exists) and ruled out routing through nova-tenant-controller's Postgres, since that would force this Python library through a Node.js service call on every credential read. No open questions remain. |
Problem Statement¶
Novaworks wants outside vendors to be able to register their own A2A-speaking agents so that Nova services can call on them to complete specialized tasks — with the vendor's Agent Card describing what that agent can do. Today there is no shared mechanism for any Nova Python service to open a session with such an externally-registered agent and stream its response back; each consumer would have to build its own Agent Card resolution, credential handling, and A2A streaming client from scratch.
Use Cases¶
Use Case 1: A customer's ServiceNow instance has a registered external agent (added by that customer or a vendor) tagged against one or more Extension Points — for example, an agent usable for the
Transfer Agentsextension point oncehuman-escalation-context-handoff(inai-guardrails-sensitive-routing) needs a Receiving Agent, or an agent usable for aConversation Listenersextension point once that extension point is built. Whichever Nova service has already decided to call that agent needs a way to actually open the session and get a streamed result back — today it would have to build that itself.Use Case 2: A functional agent other than Super Agent (or a UI's backend) wants to delegate a specialized sub-task to a registered external agent. It needs the identical underlying session mechanism as Use Case 1, reused rather than rebuilt per consumer.
Scope¶
This Feature is infrastructure: a library that opens a streaming session with a specific, already-identified external A2A agent and relays its response back, plus the two ServiceNow-backed reference tables the library reads from.
- Open a session with a named external agent. Given a tenant, a specific registered external agent's identifier, and a task description, resolve that agent's Agent Card and registered credential, open an A2A streaming session (
message/stream), and stream response-chunk text back to the caller until the task pauses or reaches a terminal state. - Agent Card is parsed at runtime, not pre-processed at registration. The library reads the stored Agent Card content on every call to get both the agent's endpoint URL and its declared security scheme — it needs to parse the card for the URL regardless, so the security scheme (API key vs. OAuth client-credentials) is read from the same parse rather than stored as a separate field.
input-requiredpauses and resumes; it is not terminal. If the external agent asks for more information mid-task, the library ends the current stream, returns the A2A task/context id plus the agent's prompt for what it needs, and exposes a separate resume call the consumer invokes later — whenever it has the answer — with that same task id and the additional input. No connection is held open while waiting. This can repeat for as many rounds as the agent asks; each resume call can itself pause again, fail, complete, or hitauth-required.- Three true terminal outcomes, distinguishable from each other and from a pause.
completed(success),failed(generic task failure), andauth-required(the presented credential was rejected or is otherwise insufficient) each end the session and are surfaced to the caller as distinct outcomes — a consumer needs to tell "this needs a better credential" apart from a plain failure, and both apart from the non-terminal "paused, awaiting input" state. - Two reference tables in
nova-platform-sn-app: x_novaw_platform_extension_point— catalog of extension points. This Feature only creates the table; it does not seed it. Other Nova SN apps add rows to it over time, based on their own capabilities, as those extension points are actually built.x_novaw_platform_external_agent— registered external agents: name, the Agent Card's full content (stored directly, not a URL fetched live), a service-account credential reference, and a many-to-many link to the Extension Point(s) it's usable from (one external agent can serve more than one Extension Point).- Callable by any Nova Python service. Super Agent, another functional agent, or a UI's backend (as a server-side import, not a browser-side call) can all call this library the same way — it doesn't privilege any one caller.
- "Thoughts" are dropped. Only response-chunk content is relayed to the caller; any reasoning/thought parts in the A2A stream are ignored in this version.
- Extensible by data, not code. A new Extension Point, or a new External Agent registered against one or more Extension Points, is a row in one of the two tables — never a code change to this library.
- Lives in a new subpackage in
nova-agent-utils. Not folded into an existing subpackage — this library gets its own, since it's a distinct concern (external A2A agent sessions) from what's already there. - Durable credential storage is a new Postgres table, KMS-encrypted.
x_novaw_platform_external_agent's credential reference resolves to a row in a new Postgres table (envelope-encrypted via KMS), not to ServiceNow itself — ServiceNow's own Credential Store has no external-facing read path (confirmed: no OOB REST endpoint returns a decrypted secret to a non-ServiceNow caller; the only workaround is a custom Scripted REST API proxy, which ServiceNow's own guidance flags as a security anti-pattern) — and not routed throughnova-tenant-controller's existing credential storage either, since that would force every consumer of this Python library through a Node.js service call just to resolve a secret. This table lives wherever the Python services already share database access, so any consumer reads it directly. - Build on the official
a2a-sdkPython package, not a hand-rolled A2A client.A2ACardResolver/A2AClientfor card parsing andmessage/send/message/stream, andTaskStatefor thecompleted/failed/input-required/auth-requiredvocabulary this spec relies on, all come from that package — it should not be reimplemented. Flagging this explicitly because it isn't derivable from this spec's business framing alone; call this out again in the implementation plan so it isn't missed.
Out of Scope¶
- Populating either table. This Feature only creates
x_novaw_platform_extension_pointandx_novaw_platform_external_agent— it does not seed the extension point catalog (other Nova SN apps add their own rows as they build the capability that extension point represents) and does not populate external agent registrations (registration UX, vendor vetting, and onboarding review are not this library's concern). It only reads whatever rows exist at call time. - Per-user authorization. This version authenticates every session with one shared service-account credential per tenant per external agent — there is no per-user linked-account mode. Per-user identity (each end user having their own credential at the external agent) is real and needed, but is explicitly deferred to a future version — related design work (account-linking via OAuth, a per-user credential store) already exists in prior A2A outbound-delegation planning and should be revisited then, not built now.
- Webhook/push-notification delivery. Resuming a paused task is always caller-initiated (the consumer calls the resume method once it has an answer) — this library never pushes a notification to the consumer when a task is paused or when it's time to resume. Surfacing an
input-requiredprompt to an actual human (chat message, notification, etc.) is each consuming service's own concern. - Tracking or expiring abandoned paused tasks. If a consumer never calls resume after a task pauses for input, this library does not time it out, clean it up, or notify anyone — that task simply sits paused on the external agent's own side, subject to whatever task-lifetime policy that agent enforces (which Novaworks does not control).
- Non-Python consumers. This is a Python library. A UI whose backend isn't Python (e.g. a Node.js or ServiceNow-scoped-app backend) is not addressed by this version — if that need arises, it's a future decision, not this Feature's.
- Agent selection / routing by Extension Point. The calling service already knows which specific external agent to invoke (e.g. because its own tool-calling or business logic already chose one) — this library does not choose an agent on the caller's behalf.
- Which Nova services actually adopt this library. Whether/when Super Agent, a specific functional agent, or a specific UI backend integrates this library is each consumer's own future work, not part of this Feature.
Value¶
One reusable external-agent session mechanism — resolve a registered agent, authenticate to it per its declared scheme, stream its response back — means every Nova service that wants to call an externally-registered A2A agent reuses infrastructure proven once, instead of each building its own Agent Card resolution, credential handling, and streaming client. This is the foundation every Coalition of the Willing extension point (Transfer Agents' eventual live-session step, External Agent Insertion, Conversation Listeners, and extension points not yet scoped) can build on, rather than each inventing its own external-agent-calling mechanism from zero.
Delivery¶
UX Description¶
This Feature has no end-user UI of its own — it's a library called by whichever Nova Python service has already decided to invoke a specific registered external agent.
Key Flows¶
Flow 1 — Consumer opens a session with a registered external agent:
1. Entry point: a Nova Python service (Super Agent, another functional agent, or a UI's backend) has already decided which registered external agent to call and has a task to hand it.
2. Consumer calls this library's open method with the tenant, the external agent's identifier, and the task text.
3. Library resolves the x_novaw_platform_external_agent record — its Agent Card content and its service-account credential reference.
4. Library parses the Agent Card at runtime to get the agent's endpoint URL and its declared security scheme, resolves the credential reference to the actual secret, authenticates outbound accordingly, and opens an A2A streaming session for the task.
5. Library relays response-chunk content back to the consumer as it streams in, dropping any "thought"/reasoning parts.
6. Exit point: the underlying A2A task reaches completed, failed, or auth-required (three terminal outcomes, surfaced distinctly) and the stream ends — or it reaches input-required, in which case the library ends the stream and returns the task/context id plus the agent's prompt for what it needs, without treating this as an error.
Flow 2 — Consumer resumes a paused task:
1. Entry point: the consuming service now has an answer to what the agent asked for in Flow 1 (from a human, from its own logic, however long that takes) — the connection from Flow 1 is long since closed.
2. Consumer calls this library's resume method with the tenant, the external agent's identifier, the task/context id from the pause, and the additional input.
3. Library re-authenticates the same way as Flow 1 (steps 3–4) and sends the additional input against the existing task.
4. Library relays response-chunk content back as it streams in, same as Flow 1.
5. Exit point: same four possible outcomes as Flow 1 — if input-required again, Flow 2 repeats for as many rounds as the agent asks; otherwise the task ends on completed, failed, or auth-required.
Human Override & Fallback Strategy¶
Not applicable in this version — there is no human-in-the-loop step inside a session; the consuming service is responsible for anything it wants to do with the streamed result.
AI Feedback Loop¶
Not applicable — this library does not generate content itself; it relays whatever the external agent streams back.
Acceptance Criteria¶
- Given a tenant, a registered external agent's identifier, and a task description, the library opens an A2A session using that agent's registered service-account credential and streams back only response-chunk text until the task pauses or reaches a terminal state.
- When a task pauses on
input-required, the consumer can later resume it — with the same task/context id plus additional input — any number of times, without holding a connection open in between. - The caller can distinguish
completed,failed,auth-required(three terminal outcomes) from the non-terminal "paused, awaiting input" state, rather than receiving one generic error for all four. - A new Extension Point can be added to
x_novaw_platform_extension_pointwithout a code change. - A new External Agent can be registered against one or more Extension Points in
x_novaw_platform_external_agentwithout a code change to this library. - This library performs no agent selection/routing and no per-user authorization — both remain explicitly out of scope for this version.
Dependencies / Related Features¶
- Other Nova SN apps are the ones expected to add rows to
x_novaw_platform_extension_pointas they build the extension points named conceptually in the parent concept.md (Transfer Agents, External Agent Insertion, Conversation Listeners, Portal Widgets, Custom Guardrails, Functional Agent Registration, Policy/Info Providers) — this Feature does not seed that table itself. ai-guardrails-sensitive-routing(human-escalation-context-handoff) and any other Coalition of the Willing extension point are future consumers of this library once built — not part of this Feature.- Prior A2A outbound-delegation planning work (Superagent-specific, superseded in scope by this rewrite) already sketched per-user identity/account-linking and durable-secret-storage options — revisit that work when per-user auth is scheduled, rather than re-deriving it from scratch. That same planning work is also where the
a2a-sdkpackage choice (see Scope) was originally verified against the officiala2aprojectGitHub repos — no need to re-verify the package choice from scratch either.
Risks¶
| Risk | Severity | Mitigation |
|---|---|---|
| Durable credential storage is a new Postgres table with its own KMS envelope-encryption setup — new schema and a new encryption path to build and secure correctly, unlike reusing something already in production | Medium | Standard KMS envelope-encryption pattern (already proven elsewhere in Nova, e.g. nova-tenant-controller's oauth_clients.client_secret column) — a known pattern to replicate, not a novel one to invent. |
| Agent Card content is stored directly, not fetched live — a vendor's real capabilities can drift from the stored card with no automatic detection | Medium | Out of scope to auto-detect or remediate in this version; whoever maintains a registration is responsible for keeping its stored card current. A future sync/monitoring job could automate this later. |
| The two reference tables are tenant-scoped but populated entirely outside this Feature's control, with no defined owner in this version | Low | This Feature only reads what's there; population and vetting are explicitly out of scope. |
| A paused task that never gets resumed lingers indefinitely on the external agent's side, with no visibility from the Nova side | Low | Explicitly out of scope for this version (see Out of Scope) — the external agent's own task-lifetime policy governs, not Novaworks. |
Open Questions¶
No open questions at this time.
For internal engineering review only.
Novaworks AI | Confidential