docs(a2a): client exposures, URLs without queries, README/architecture

This commit is contained in:
EugeneTes
2026-10-08 14:42:18 +00:00
parent 1817272b73
commit 923a6e04ac
6 changed files with 59 additions and 6 deletions

View File

@@ -64,6 +64,10 @@ malformed task file is read as "no such task" (`TaskNotFound`) without a log lin
Without it: name from `A2A:Card:Name`, description from the first non-empty line of the workspace's `PERSONA.md`
(else `A2A:Card:Description`), and one generic `chat` skill.
The [client](#client) keeps its own files in the same directory: `agents.json` (the agents this workspace's assistant
may call, with their keys; see [agents.json](#agentsjson)) and `pending.json` (its watches of remote tasks; see
[Watches](#watches)).
`tasks/<taskId>.json`: one task per file (the SDK's `AgentTask` plus which caller owns it and its push configs),
written atomically (temp file, then rename) and read fresh on every request.
@@ -154,7 +158,11 @@ zone into its sessions (`docs/telegram-bot.md#a2a`); a host that embeds the libr
- reading `.volcanic/a2a/callers.json` — every caller's plaintext key — and editing it, i.e. minting itself (or
anyone) new keys or changing other callers' entries;
- writing `.volcanic/scheduler/jobs.json` or `.volcanic/triggers/triggers.json`, whose prompts later run in the
owner's **main** session — the one that has the calendar and the owner's history.
owner's **main** session — the one that has the calendar and the owner's history;
- with the [client](#client) on, forging `.volcanic/a2a/pending.json`: a record with an `undelivered` text runs as a
prompt in the owner's chat on the next restart or session start;
- editing `.volcanic/a2a/agents.json` to point an agent name somewhere else, so the owner's next `send` to that
name — message, attachments and the configured key — goes there (and reading the keys in it).
These are accepted v1 exposures: the owner chose plain files with no protection. Give A2A keys only to agents you
would trust with that workspace.
@@ -215,7 +223,8 @@ Off (the default), no session gets the tool, no watches are restored, and `/a2a`
Only the owner's own chats get the `a2a` tool: the Telegram host adds `A2AClientExtension` to each allowed chat's
session (target = the chat id), never host-wide. So the isolated sessions the server opens for inbound A2A callers
(untrusted) can't use the keys in `agents.json` to call other agents. The startup log shows
(untrusted) can't call other agents through the tool (their `bash` can still read the keys in `agents.json`; see
[Client security](#client-security)). The startup log shows
`A2A client: on, agents in each chat's .volcanic/a2a/agents.json, watches subscribe or poll` (or `push at <url>`).
### agents.json
@@ -350,8 +359,10 @@ configured subscribes instead. On shutdown the watches stop and their records st
Every outgoing message carries `metadata["volcanic.hops"]` = the session's inbound hop count + 1. The count comes from
the `a2a.inbound` event the server's per-session extension emits before each inbound prompt; a session with no inbound
A2A counts 0. A Volcanic server refuses a task over its `MaxHops` (3), which stops two assistants delegating to each
other forever. In Telegram only the owner's chats have the tool, so their messages always carry 1; the listener
matters for hosts that add the extension to sessions that also receive A2A.
other forever. In Telegram only the owner's chats have the tool, so their messages always carry 1 and the listener is
inactive: the remote side's `MaxHops` (or whatever guard a non-Volcanic remote has) is the only thing that bounds a
chain of agents delegating onwards. The listener matters for hosts that add the extension to
sessions that also receive A2A.
### Client configuration
@@ -372,6 +383,10 @@ ignores any configured value otherwise.
- **`agents.json` keys are plaintext in the workspace**, like `callers.json`: anything that reads the workspace (the
agent's `bash`, an inbound A2A caller's isolated session, a backup) can read them.
- **An inbound A2A caller's isolated session can tamper with the client's files** through `bash` in the shared
workspace: forge `pending.json` (an `undelivered` text runs as a prompt in the owner's chat on restart or session
start) or redirect an agent name in `agents.json` (the owner's next `send`, its attachments and the key go to the new
URL). Accepted v1 exposures, like `jobs.json` (see [Security](#security)).
- **The agent chooses where data goes.** It can edit `agents.json` like any workspace file, so it can add an agent with
any URL (including internal addresses; the client's HTTP has no private-range guard) and send it workspace files.
That is the same trust it already has through `bash` (`curl`), not a new boundary; treat the `a2a` tool accordingly.

View File

@@ -6,7 +6,7 @@
src/Volcanic.AgenticCore.Abstractions contracts (no runtime code except helpers)
src/Volcanic.AgenticCore runtime, organised as vertical slices
src/Volcanic.AgenticCore.A2A.Server A2A protocol server on the official SDK, built on the core
extensions/* provider and feature extensions (MCP client, Google Calendar, Scheduler), built on the public API only
extensions/* provider and feature extensions (MCP client, Google Calendar, Scheduler, A2A client), built on the public API only
samples/* reference hosts
tests/* xunit
```
@@ -26,6 +26,12 @@ directories.
on a `TimeProvider` timer, jobs in each target's workspace, `.volcanic/scheduler/jobs.json`), a per-session `schedule` tool bound to a
host target, and an `IJobDelivery` the host implements (see [telegram-bot.md](telegram-bot.md#scheduling)).
`A2A` is the A2A client, a feature extension: a per-session `a2a` tool bound to a host target over the workspace's
`.volcanic/a2a/agents.json`, and a process-wide `A2AWatchRegistry` that follows long remote tasks (push, subscribe or
poll), persists them in `.volcanic/a2a/pending.json` and hands results to an `IA2AResultDelivery` the host may
implement (default: the target's live session). Hosts add it per session, never host-wide, so A2A server sessions
don't get it (see [a2a.md](a2a.md#client)).
`Volcanic.AgenticCore.A2A.Server` exposes assistants over [A2A](a2a.md) on the official `A2A`/`A2A.AspNetCore` SDK.
It depends on the core (`IAgentSessionFactory`, `AgentSessionOptions`) directly, not through the extension API,
since it needs ASP.NET Core hosting the extension contract doesn't carry. Tenancy is structural: the SDK's

View File

@@ -230,5 +230,13 @@ What the code does where it differs from the sections above (the user guide, `do
- **Attachments**: paths are confined to the workspace after resolving symlinks and capped together at
`MaxFileBytes`; special files (FIFOs, devices) aren't detected, so a FIFO blocks the call until the run is aborted.
- **`IA2AResultDelivery.WorkspaceFor` returns `string?`** (null: unknown target).
- **Accepted v1 exposures** (like the server's `jobs.json` ones): an inbound A2A caller's isolated session shares the
workspace and has `bash`, so it can read the keys in `agents.json`, redirect an agent name there (the owner's next
`send`, attachments and key go to the new URL), and forge `pending.json` (an `undelivered` text runs as a prompt in
the owner's chat on restart or session start). It can't call other agents through the tool. In Telegram the hop
listener is inactive (only the owner's chats have the tool, so every message carries 1); the remote's `MaxHops` is
the only chain guard.
- **`/a2a` shows agent URLs as scheme, host, port and path only** (no query, fragment or user info, which may carry
credentials).
- **Docker**: the `Dockerfile` restore layer gained the A2A server and client project files (the server's was missing,
which already broke the image build).