agentscope-ai / agentscope-ai/QwenPaw

[Feature] Inject per-message context into shell tool subprocess env

Ouverte
#3,825 2 commentaires 0 réactions 1 personne assignée Réclamée par @xieyxclack Voir sur GitHub
Langage dominant
Python
Étoiles
34.9k
Forks
3.1k
Merge moyen
1 j 15 h
PR mergées (30 j)
225

Description

## Summary

When the LLM agent invokes the shell tool to run a skill script, the
subprocess inherits a static `os.environ.copy()` snapshot from the
QwenPaw worker process. Per-message context (Matrix sender, room id,
session id) reaches the LLM via `build_env_context(...)`
(system-prompt string), but **does not** reach the subprocess as env
— making it impossible for skill scripts to attribute the current
invocation to the originating user.

## Component(s) Affected

- [x] Core / Backend (`agents/tools/shell.py`)
- [x] Skills (downstream consumers of per-message attribution)

## Problem / Motivation

In multi-tenant scenarios where one Worker serves many users, audit
trails and access checks performed by skill scripts need a reliable
way to know "who triggered this invocation". The data is already in
the runtime payload:

- `copaw/src/matrix/channel.py:1571` constructs
`payload.meta = {room_id, sender_id, event_id, ...}`
- `qwenpaw/app/runner/runner.py:350` already calls
`build_env_context(session_id=..., user_id=..., channel=..., ...)`

But `qwenpaw/agents/tools/shell.py:327` does:

```python
env = os.environ.copy()
...
proc = await asyncio.create_subprocess_shell(cmd, ..., env=env, ...)
```

— a process-wide snapshot, not per-message. So the LLM sees the
user_id but the subprocess does not.

## Proposed Solution

Merge a per-call context dict on top of `os.environ.copy()` before
fork. The dict could be sourced from a contextvar populated by the
runner just before the shell tool is invoked (or any other mechanism
the maintainers prefer).

Suggested env names (project-prefixed, naming aligned with existing
runner fields):

- `QWENPAW_USER_ID` — user identity (always present once known)
- `QWENPAW_SESSION_ID` — session identity (always present)
- `QWENPAW_CHANNEL` — channel name (matches the existing `channel`
argument to `build_env_context`); always present
- `QWENPAW_ROOM_ID` — **optional**; set when available (Matrix and
similar room-based channels). Not all channels have this concept
- `QWENPAW_EVENT_ID` — **optional**; set when available (Matrix
event ID). Not all channels expose a stable event id

Skill scripts that don't care can ignore them; scripts needing
attribution can read them via `os.environ.get(...)`.

## Why not LLM-side splicing

A tempting alternative is asking the LLM to splice
`USER_ID=` into bash invocations from `build_env_context`'s
prompt text. We considered and rejected this for our project: audit
fields written by an LLM are prompt-injectable and unreliable; a fake
user_id is worse than absent attribution. Runtime-level injection is
the only design that audit infrastructure can actually trust.

## Use case

Downstream project (haopaw, multi-agent platform on HiClaw v1.1.0
GA): domain-service skills sign HMAC confirmation tokens and need the
originating Matrix user in their audit logs. Currently the skill
scripts fall back to `"unknown"` for actor headers — functional, but
loses traceability for compliance review.

## Acceptance test

Within a skill script:

- `os.environ.get("QWENPAW_USER_ID")` matches the message originator
(e.g. Matrix `event.sender`)
- Concurrent messages in different rooms must not see each other's
values
- For channels that do not expose `room_id` / `event_id`,
`QWENPAW_ROOM_ID` / `QWENPAW_EVENT_ID` should be **unset** (not
`"unknown"` or `""`). The runtime injection layer should never
manufacture placeholder values; downstream code is responsible for
whatever fallback policy makes sense for it.

Guide de contribution

Ouvrir le guide de contribution

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.