Skip to content

phantom: inject tenant self-knowledge into system prompt overlay (Phase 9)#112

Merged
mcheemaa merged 1 commit intomainfrom
feat/2026-05-01-phase9-self-knowledge
May 1, 2026
Merged

phantom: inject tenant self-knowledge into system prompt overlay (Phase 9)#112
mcheemaa merged 1 commit intomainfrom
feat/2026-05-01-phase9-self-knowledge

Conversation

@mcheemaa
Copy link
Copy Markdown
Member

@mcheemaa mcheemaa commented May 1, 2026

Summary

Inject per-tenant identity into the agent's system prompt so it knows its own URL, owner, dashboard, runtime, and model. Mission v1 sequencing step 4 (master plan section 3 Phase 9).

The overlay slots between Identity and Environment in src/agent/prompt-assembler.ts, reads non-secret env vars stamped into the process by phantom-firstboot, and degrades to the empty string in single-tenant or laptop dev mode.

What changed

  • New src/agent/prompt-blocks/tenant-self-knowledge.ts exporting buildTenantSelfKnowledge() and readTenantSelfKnowledgeEnv().
  • prompt-assembler.ts now slots the overlay as section 1b, between Identity and Environment.
  • CLAUDE.md documents the overlay, the env var contract, and the source of each var.
  • 23 unit tests (builder + reader) + 4 integration tests (assembler position + defensive shape + integration hooks).

Env vars consumed

All non-secret tenant identifiers; the overlay never carries provider keys, OAuth tokens, or other secrets.

Env var Source Phase
PHANTOM_TENANT_SLUG phantomd firstboot, currently injected Phase 1+
PHANTOM_TENANT_ID phantomd firstboot, currently injected Phase 1+
PHANTOM_OWNER_EMAIL phantomd firstboot, currently injected Phase 1+
PHANTOM_OWNER_NAME phantomd firstboot, queued Phase 1+ (one-line addition)
PHANTOM_DOMAIN phantomd firstboot, queued Phase 1+ (one-line addition); falls back to <slug>.phantom.ghostwright.dev
PHANTOM_DASHBOARD_URL phantom-rootfs systemd unit shipped
PHANTOM_AGENT_RUNTIME phantom-rootfs systemd unit (Phase 0) or tenant.env (Phase 1) shipped
PHANTOM_MODEL tenant.env (Phase 1 wizard) shipped
PHANTOM_GRANTED_INTEGRATIONS hook only; silent today Phase 7
PHANTOM_CHANNEL_ALLOWLIST hook only; silent today Phase 8b

Failure-path coverage

The overlay is defensive against intermediate phantomd versions: every line is gated on a real value, so missing optional vars silently disappear. Specific cases covered by tests:

  • All vars unset: overlay returns the empty string; assembler drops the section.
  • Only slug + email + dashboard set: overlay still produces a useful block; home URL falls back to <slug>.phantom.ghostwright.dev.
  • Owner phrase falls back to email-only, name-only, or omits entirely if both are absent.
  • PHANTOM_DOMAIN=https://example.com/ (with scheme/trailing slash): stripped to bare host before composing.
  • Whitespace-only env values treated as unset.
  • Empty / whitespace-only list values for PHANTOM_GRANTED_INTEGRATIONS and PHANTOM_CHANNEL_ALLOWLIST produce no line.

Refresh / restart survival

The C7 invariant (tenant.env consumed once at firstboot, then deleted) does not affect this overlay: phantom-firstboot stamps these values into /etc/default/phantom, which phantom.service sources via EnvironmentFile=, so they survive in process.env for the lifetime of the agent process across systemd restarts.

Coordination with phantomd

No phantomd or phantom-rootfs change is required for this PR. PHANTOM_OWNER_NAME and PHANTOM_DOMAIN are queued as a one-line addition to phantomd/internal/state/orchestrator.go firstbootStep (per phantomd CLAUDE.md "What's queued"); when those land, the overlay starts surfacing them automatically with no Phantom-side change.

Test plan

  • bun test src/agent/prompt-blocks/__tests__/tenant-self-knowledge.test.ts (23 tests)
  • bun test src/agent/__tests__/prompt-assembler.test.ts (22 tests)
  • bun test full suite: 2152 pass, 0 fail, 10 skip, 1 todo
  • bun run lint clean
  • bun run typecheck clean
  • Codex review round
  • Human verification of overlay text in a real tenant after merge

Out of scope

  • phantomd-side injection of PHANTOM_OWNER_NAME and PHANTOM_DOMAIN (queued separately per phantomd CLAUDE.md).
  • Phase 7 / Phase 8b emission of granted integrations and channel allowlist.
  • Magic-link auth (Phase 6) and per-agent grants UI (Phase 7).

@codex review

Inject per-tenant identity into the agent's system prompt so it knows its
own URL, owner, dashboard, runtime, and model. The overlay slots between
Identity and Environment, reads non-secret env vars stamped into the
process by phantom-firstboot, and degrades to the empty string in
single-tenant or laptop dev mode.

Vars consumed: PHANTOM_TENANT_SLUG, PHANTOM_TENANT_ID, PHANTOM_OWNER_EMAIL,
PHANTOM_OWNER_NAME, PHANTOM_DOMAIN, PHANTOM_DASHBOARD_URL,
PHANTOM_AGENT_RUNTIME, PHANTOM_MODEL, plus the PHANTOM_GRANTED_INTEGRATIONS
(Phase 7) and PHANTOM_CHANNEL_ALLOWLIST (Phase 8b) hooks. Every line is
gated on a real value so missing optional vars silently disappear; the
block never carries provider keys or secrets.

Tests: 23 unit tests on the builder + reader + 4 integration tests on the
assembler covering position (Identity < overlay < Environment), defensive
shape (slug + dashboard only), full-env shape, and the integration hooks.
Failure-path coverage for missing env vars, whitespace-only inputs,
malformed list values, and the no-tenant fallback.

Mission v1 sequencing step 4 (master plan section 3 Phase 9). No
phantomd / phantom-rootfs change required: the overlay is defensive
against intermediate phantomd versions that have not yet landed
PHANTOM_DOMAIN and PHANTOM_OWNER_NAME injection.
@mcheemaa
Copy link
Copy Markdown
Member Author

mcheemaa commented May 1, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 73cfc97a0a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// "you are the Phantom assigned to X's workspace `slug`" as a unit.
const ownerPhrase = composeOwnerPhrase(e.ownerName, e.ownerEmail);
if (ownerPhrase && e.tenantSlug) {
lines.push(`You are the Phantom assigned to ${ownerPhrase}'s workspace \`${e.tenantSlug}\`.`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Escape tenant env values before composing prompt lines

buildTenantSelfKnowledge interpolates environment values directly into the system prompt, so a newline/control-character payload in fields like PHANTOM_OWNER_NAME, PHANTOM_MODEL, or future tenant-provided values can inject extra instructions into the highest-priority prompt block. This is a security/integrity regression because these variables are operational inputs, not trusted static literals, and a malformed value would affect every request until restart.

Useful? React with 👍 / 👎.

Comment on lines +113 to +117
const hasAnything = Boolean(
e.tenantSlug ||
e.ownerEmail ||
e.ownerName ||
e.domain ||
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid emitting overlay header from non-renderable domain values

The presence check treats any non-empty PHANTOM_DOMAIN as sufficient to render the overlay, but composeHomeUrl can later discard values like "https://" (or scheme-only/trailing-slash variants), leaving only the section header ("# Who You Are In This Workspace") and no content. In misconfigured or partially rolled-out environments this produces a stub prompt block instead of the intended silent fallback.

Useful? React with 👍 / 👎.

@mcheemaa mcheemaa merged commit 2e6e4ec into main May 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant