docs(a2a): client exposures, URLs without queries, README/architecture
This commit is contained in:
23
docs/a2a.md
23
docs/a2a.md
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user