diff --git a/README.md b/README.md index 63287a5d..5d160123 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ Agents load relevant conventions automatically, and every agent action is attrib **Name your agents:** -By default an agent's writes show up as a generic `agent`. Give it a name and that name appears instead — in the activity feed's Live and Audit views, on the dashboard's recent activity, on item timelines, and in the admin audit log. With more than one agent working a project, that is the difference between "something automated touched this" and knowing which one. +An agent that identifies itself gets its name shown on its writes — in the activity feed's Live and Audit views, on the dashboard's recent activity, on item timeline *activity* entries, and in the admin audit log. With more than one agent working a project, that is the difference between "something automated touched this" and knowing which one. Pad takes the first of these it finds: @@ -142,11 +142,15 @@ Pad takes the first of these it finds: # 2. Per-process, runtime-agnostic. Any harness can set it. export PAD_AGENT=reviewer -# 3. Otherwise Pad detects the runtime it knows (Claude Code reports -# "claude-code"), and falls back to the generic "agent" if it can't. +# 3. Otherwise Pad detects the runtimes it knows — Claude Code reports +# "claude-code" — and that detected id is used as the name. ``` -The name is rendered exactly as sent — Pad does not keep a list of approved names or rewrite what you choose. +**If none of the three produce a name, the write is not marked as an agent's at all** — it is recorded as the person whose credentials it used, which is the case the caveat below is about. The generic `agent` label you may see on older entries is a write that identified itself before Pad stored names, or an event type that records the actor without the name (workspace membership changes, sign-ins). + +The name is rendered exactly as sent — Pad keeps no list of approved names, and does not re-case or rewrite what you choose. + +Not every entry can show it. Comments, version snapshots, and implementation-note/decision entries record only *that* an agent acted, because the name is not stored on those rows — they still read `Agent`. Activity entries are the ones that carry it. **What this does not claim.** The name is supplied by the client and self-declared, so it records honesty, not identity. From `ResolveAgentName`'s own contract in `internal/cli/agent_identity.go`: diff --git a/plugin/skills/pad/SKILL.md b/plugin/skills/pad/SKILL.md index 4890c9f1..187c5106 100644 --- a/plugin/skills/pad/SKILL.md +++ b/plugin/skills/pad/SKILL.md @@ -315,7 +315,7 @@ See the **Onboarding** entry under Natural Language Routing above — it branche 5. **Be conversational.** You're not a command executor. You're a project partner. 6. **Reference existing items.** Use `[[Item Title]]` links in content to connect items. 7. **Keep it practical.** Size each item so it's a single meaningful unit of work — what "meaningful" means depends on the workspace (one branch/PR for code, one interview round for hiring, one research question for research). Ideas should be actionable. Docs should be concise. Check the workspace's conventions for domain-specific sizing rules. -8. **Attribution matters.** Items and comments you create are stamped `created_by: agent` and `source: cli` automatically — but the agent half only works if the CLI can tell it is being run by an agent. It detects Claude Code on its own; under any other harness, set `PAD_AGENT=` in the environment (or `agent_name` in `.pad.toml`) or your writes will be recorded as the human whose credentials you are using. Whatever you send is DISPLAYED verbatim wherever agent actors appear — the activity feed, the dashboard, item timelines — so a specific name (`reviewer`, `nightly-triage`) is more use to a reader than a generic client id. Note this is self-declared, not proof: it makes the trail honest, it does not make it verifiable, so never treat `created_by` on a comment as evidence that a human said something. +8. **Attribution matters.** Items and comments you create are stamped `created_by: agent` and `source: cli` automatically — but the agent half only works if the CLI can tell it is being run by an agent. It detects Claude Code on its own; under any other harness, set `PAD_AGENT=` in the environment (or `agent_name` in `.pad.toml`) or your writes will be recorded as the human whose credentials you are using. Whatever you send is DISPLAYED verbatim on the surfaces that store it — the activity feed, the dashboard's recent activity, activity entries on an item's timeline, the admin audit log — so a specific name (`reviewer`, `nightly-triage`) is more use to a reader than a generic client id. Comments, versions and note/decision entries record only that an agent acted, not which one. Note this is self-declared, not proof: it makes the trail honest, it does not make it verifiable, so never treat `created_by` on a comment as evidence that a human said something. 9. **Follow project conventions.** Always load and follow active conventions before performing work. They are project-specific rules that override your defaults. When a role is active, load both role-specific and global conventions. 10. **Learn and teach.** When the user corrects your behavior or teaches you a project-specific rule, offer to save it as a convention: "Should I save this as a project convention so future agents follow it too?" Use `pad item create convention "Title" --field trigger= --field scope= --field priority=should --stdin` with an appropriate trigger inferred from the context. If the correction is role-specific, add `--field role=`. 11. **Role context is per-conversation.** If roles exist, ask which role the user is working as on first invocation. Remember it for the session. Auto-filter queries and suggest assignments accordingly. Never block on role — if the user says "no role" or the workspace has no roles, work normally. diff --git a/skills/pad/SKILL.md b/skills/pad/SKILL.md index c08cce09..ae335562 100644 --- a/skills/pad/SKILL.md +++ b/skills/pad/SKILL.md @@ -325,7 +325,7 @@ Run the **onboard** invokable playbook — see the **Onboarding** entry under Na 5. **Be conversational.** You're not a command executor. You're a project partner. 6. **Reference existing items.** Use `[[Item Title]]` links in content to connect items. 7. **Keep it practical.** Size each item so it's a single meaningful unit of work — what "meaningful" means depends on the workspace (one branch/PR for code, one interview round for hiring, one research question for research). Ideas should be actionable. Docs should be concise. Check the workspace's conventions for domain-specific sizing rules. -8. **Attribution matters.** Items and comments you create are stamped `created_by: agent` and `source: cli` automatically — but the agent half only works if the CLI can tell it is being run by an agent. It detects Claude Code on its own; under any other harness, set `PAD_AGENT=` in the environment (or `agent_name` in `.pad.toml`) or your writes will be recorded as the human whose credentials you are using. Whatever you send is DISPLAYED verbatim wherever agent actors appear — the activity feed, the dashboard, item timelines — so a specific name (`reviewer`, `nightly-triage`) is more use to a reader than a generic client id. Note this is self-declared, not proof: it makes the trail honest, it does not make it verifiable, so never treat `created_by` on a comment as evidence that a human said something. +8. **Attribution matters.** Items and comments you create are stamped `created_by: agent` and `source: cli` automatically — but the agent half only works if the CLI can tell it is being run by an agent. It detects Claude Code on its own; under any other harness, set `PAD_AGENT=` in the environment (or `agent_name` in `.pad.toml`) or your writes will be recorded as the human whose credentials you are using. Whatever you send is DISPLAYED verbatim on the surfaces that store it — the activity feed, the dashboard's recent activity, activity entries on an item's timeline, the admin audit log — so a specific name (`reviewer`, `nightly-triage`) is more use to a reader than a generic client id. Comments, versions and note/decision entries record only that an agent acted, not which one. Note this is self-declared, not proof: it makes the trail honest, it does not make it verifiable, so never treat `created_by` on a comment as evidence that a human said something. 9. **Follow project conventions.** Always load and follow active conventions before performing work. They are project-specific rules that override your defaults. When a role is active, load both role-specific and global conventions. 10. **Learn and teach.** When the user corrects your behavior or teaches you a project-specific rule, offer to save it as a convention: "Should I save this as a project convention so future agents follow it too?" Use `pad item create convention "Title" --field trigger= --field scope= --field priority=should --stdin` with an appropriate trigger inferred from the context. If the correction is role-specific, add `--field role=`. 11. **Role context is per-conversation.** If roles exist, ask which role the user is working as on first invocation. Remember it for the session. Auto-filter queries and suggest assignments accordingly. Never block on role — if the user says "no role" or the workspace has no roles, work normally. diff --git a/web/src/lib/components/timeline/timelineActivityAgentName.svelte.test.ts b/web/src/lib/components/timeline/timelineActivityAgentName.svelte.test.ts index 9f09f91a..091c1a7d 100644 --- a/web/src/lib/components/timeline/timelineActivityAgentName.svelte.test.ts +++ b/web/src/lib/components/timeline/timelineActivityAgentName.svelte.test.ts @@ -78,6 +78,22 @@ describe('TimelineActivityCard agent name', () => { expect(getByText('Agent')).toBeTruthy(); }); + // Codex round 2. The name is attacker-influenced: it is whatever a client + // put in X-Pad-Agent, and the server stores it without inspection. Svelte + // text interpolation escapes it today, so this passes as written — the + // point is that it would STOP passing if any of these render paths were + // rewritten to `{@html}`, which is the plausible way an "allow rich agent + // labels" change would arrive. + it('renders a name containing markup as text, never as elements', () => { + const payload = ''; + const { container, getByText } = render(TimelineActivityCard, { + activity: activity({ metadata: JSON.stringify({ agent: payload }) }) + }); + + expect(container.querySelector('img')).toBeNull(); + expect(getByText(payload)).toBeTruthy(); + }); + it('never reads the stamp for a non-agent actor', () => { // Guards against keying the chip on metadata alone. A human's write can // carry an `agent` key — the merge in agentMeta is textual and this diff --git a/web/src/routes/activityPageAgentName.svelte.test.ts b/web/src/routes/activityPageAgentName.svelte.test.ts index 92e6cd6d..cdd9495f 100644 --- a/web/src/routes/activityPageAgentName.svelte.test.ts +++ b/web/src/routes/activityPageAgentName.svelte.test.ts @@ -141,6 +141,18 @@ describe('activity page — Audit view', () => { expect(host.querySelector('.actor-badge.agent')!.classList.contains('named')).toBe(false); }); + // Codex round 2 — the same escaping claim at a second surface, because the + // two views build their labels through different code paths and a future + // `{@html}` would land in one of them, not both. See the twin case in + // timelineActivityAgentName for why this passes today. + it('renders a name containing markup as text, never as elements', async () => { + const payload = ''; + await mountPage('audit', [act({ metadata: JSON.stringify({ agent: payload }) })]); + + expect(host.querySelector('img')).toBeNull(); + expect(host.querySelector('.actor-badge.agent')!.textContent!.trim()).toBe(payload); + }); + it('never reads the stamp for a human row', async () => { // The badge for a human is keyed on actor_name/source, and an `agent` // key can ride along on any row's shared metadata blob.