Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ Thumbs.db
todo
.env
video
e2e_secrets.env

# Docker compose override (auto-generated by TUI)
docker-compose.override.yml

# Hugo
docs/public/
Expand Down
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 \
libasound2 libatspi2.0-0 libxshmfence1 \
libicu76 \
&& rm -rf /var/lib/apt/lists/*

# Node.js (for Playwright MCP via npx) + Copilot CLI
Expand All @@ -33,7 +34,8 @@ RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
&& rm -rf /var/lib/apt/lists/*

# Azure CLI (for automated bot provisioning)
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash \
&& az bicep install

# Docker CLI only (no daemon) -- used to push the locally-built image to ACR
RUN install -m 0755 -d /etc/apt/keyrings \
Expand Down Expand Up @@ -77,6 +79,7 @@ COPY app/cli/ app/cli/
RUN pip install --no-cache-dir --no-deps -e .
COPY skills/ skills/
COPY plugins/ plugins/
COPY infra/ infra/

# Embed the built frontend at the path _FRONTEND_DIR resolves to
COPY --from=frontend-build /build/dist/ /app/frontend/dist/
Expand Down
25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,23 @@

---

> **Warning:** Polyclaw is an autonomous agent. It can execute code, deploy infrastructure, send messages to real people, and make phone calls. The agent runtime is architecturally separated from the admin plane and operates under its **own Azure managed identity** with least-privilege RBAC -- it does **not** share your personal Azure credentials. GitHub authentication is still a prerequisite (the Copilot SDK is the agent's reasoning engine). Understand the [risks](https://aymenfurter.github.io/polyclaw/responsible-ai/) before running it.
## What's New

Polyclaw is an autonomous AI copilot built on the **GitHub Copilot SDK**. It gives you the full power of GitHub Copilot -- untethered from the IDE. It writes code, interacts with your repos via the GitHub CLI, authors its own skills at runtime, reaches out to you proactively when something matters, schedules tasks for the future, and can even call you on the phone for urgent matters.
**Foundry BYOK (Bring Your Own Key).** Polyclaw now supports using your own Azure AI Services resource for LLM inference. Set `FOUNDRY_ENDPOINT` and the agent authenticates via Entra ID bearer tokens -- no GitHub token required. The runtime service principal gets the `Cognitive Services OpenAI User` role automatically via the fix-roles endpoint.

**Bicep infrastructure deployment.** A single `infra/main.bicep` template replaces scattered Azure CLI provisioning. Deploy AI Services (with model deployments like gpt-4.1), Key Vault, Content Safety, session pools, monitoring, and more from the Setup Wizard or API. All resource creation is parameterised and idempotent.

**Default model changed to gpt-4.1.** Both `COPILOT_MODEL` and `MEMORY_MODEL` now default to `gpt-4.1`.

**Improved container HOME isolation.** The entrypoint now sets HOME to `/admin-home`, `/runtime-home`, or `/data` depending on container mode, with Bicep binary symlinking so `az bicep` works in all modes.

**Headless TUI setup.** New `app/tui/src/headless/` modules for non-interactive setup and ACA provisioning.

---

> **Warning:** Polyclaw is an autonomous agent. It can execute code, deploy infrastructure, send messages to real people, and make phone calls. The agent runtime is architecturally separated from the admin plane and operates under its **own Azure managed identity** with least-privilege RBAC -- it does **not** share your personal Azure credentials. Understand the [risks](https://aymenfurter.github.io/polyclaw/responsible-ai/) before running it.

Polyclaw is an autonomous AI copilot built on the **Copilot SDK** with **Foundry BYOK** support. It gives you the full power of AI inference through your own Azure AI Services resource -- untethered from the IDE. It writes code, authors its own skills at runtime, reaches out to you proactively when something matters, schedules tasks for the future, and can even call you on the phone for urgent matters.

## Why Polyclaw?

Expand Down Expand Up @@ -93,17 +107,16 @@ For full setup instructions, configuration reference, and feature guides, see th
## Prerequisites

- Docker
- A GitHub account with a Copilot subscription
- An Azure subscription (needed for voice, bot channels, and Foundry integration)
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) (if deploying to Azure)
- [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) (`az login` required)
- An Azure subscription (for Foundry BYOK inference, voice, bot channels, and infrastructure provisioning)

## Security, Governance & Responsible AI

Polyclaw is in **early preview**. Treat it as experimental software and read this section carefully.

### Understand the Risks

Polyclaw is an autonomous agent. The agent runtime is architecturally separated from the admin plane and operates under its **own Azure managed identity** with least-privilege RBAC -- it does **not** share your personal Azure credentials. However, it can still execute code, deploy infrastructure, send messages, and make phone calls within the scope of its assigned roles. GitHub authentication remains a prerequisite for using the Copilot SDK.
Polyclaw is an autonomous agent. The agent runtime is architecturally separated from the admin plane and operates under its **own Azure managed identity** with least-privilege RBAC -- it does **not** share your personal Azure credentials. However, it can still execute code, deploy infrastructure, send messages, and make phone calls within the scope of its assigned roles.

**What can go wrong:** unintended actions from misunderstood instructions, credential exposure via prompt injection or badly written skills, cost overruns from runaway loops provisioning Azure resources, arbitrary code execution without human review, and data leakage through conversations and tool outputs passing through configured channels.

Expand Down
10 changes: 2 additions & 8 deletions app/frontend/e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { type Page } from '@playwright/test'

export const MOCK_STATUS = {
azure: { logged_in: true, user: 'test@example.com', subscription: 'sub-123' },
copilot: { authenticated: true, username: 'testuser' },
foundry: { deployed: true, endpoint: 'https://mock-foundry.cognitiveservices.azure.com', name: 'mock-foundry' },
prerequisites_configured: true,
telegram_configured: false,
tunnel: { active: false, url: '' },
Expand All @@ -23,7 +23,7 @@ export const MOCK_STATUS = {

export const MOCK_STATUS_NEEDS_SETUP = {
azure: { logged_in: false },
copilot: { authenticated: false },
foundry: { deployed: false },
prerequisites_configured: false,
telegram_configured: false,
tunnel: { active: false },
Expand Down Expand Up @@ -453,12 +453,6 @@ export async function mockApi(page: Page) {
await page.route('**/api/setup/azure/login', route =>
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ status: 'ok' }) }),
)
await page.route('**/api/setup/copilot/login', route =>
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ status: 'ok', user_code: 'ABC-123', verification_uri: 'https://github.com/login/device' }) }),
)
await page.route('**/api/setup/copilot/status', route =>
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ authenticated: true }) }),
)
await page.route('**/api/setup/configuration/save', route =>
route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ status: 'ok' }) }),
)
Expand Down
4 changes: 2 additions & 2 deletions app/frontend/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const NAV_ITEMS = [
{ to: '/mcp', icon: '🔌', label: 'MCP Servers' },
{ to: '/schedules', icon: '📅', label: 'Schedules' },
{ to: '/profile', icon: '👤', label: 'Profile' },
{ to: '/messaging', icon: '✉️', label: 'Messaging' },
{ to: '/messaging', icon: '✉️', label: 'AI Model' },
{ to: '/infrastructure', icon: '🏗️', label: 'Infrastructure' },
{ to: '/guardrails', icon: '🛡️', label: 'Hardening' },
{ to: '/tool-activity', icon: '🔍', label: 'Tool Activity' },
Expand Down Expand Up @@ -53,7 +53,7 @@ export default function Sidebar({ status, collapsed, onToggle }: Props) {
{!collapsed && status && (
<div className="sidebar__status">
<StatusDot ok={status.azure?.logged_in} label="Azure" />
<StatusDot ok={status.copilot?.authenticated} label="GitHub" />
<StatusDot ok={status.foundry?.deployed} label="Foundry" />
<StatusDot ok={status.tunnel?.active} label="Tunnel" />
<StatusDot ok={status.bot_configured} label="Bot" />
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/frontend/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {

const LINKS = [
{ path: '/customization', label: 'Customization', Icon: IconPalette },
{ path: '/messaging', label: 'Messaging', Icon: IconSliders },
{ path: '/messaging', label: 'AI Model', Icon: IconSliders },
{ path: '/infrastructure', label: 'Infrastructure', Icon: IconSliders },
{ path: '/guardrails', label: 'Hardening', Icon: IconShield },
{ path: '/tool-activity', label: 'Tool Activity', Icon: IconActivity },
Expand Down
2 changes: 1 addition & 1 deletion app/frontend/src/hooks/useStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function useStatus(intervalMs = 30_000) {
}, [refresh, intervalMs])

const needsSetup = status
? !(status.azure?.logged_in && status.copilot?.authenticated)
? !(status.azure?.logged_in && status.foundry?.deployed)
: null

return { status, refresh, needsSetup }
Expand Down
5 changes: 2 additions & 3 deletions app/frontend/src/mock-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { IncomingMessage, ServerResponse } from 'http'

const STATUS = {
azure: { logged_in: true, user: 'test@example.com', subscription: 'sub-123' },
copilot: { authenticated: true, username: 'testuser' },
foundry: { deployed: true, endpoint: 'https://mock-foundry.cognitiveservices.azure.com', name: 'mock-foundry' },
prerequisites_configured: true,
telegram_configured: false,
tunnel: { active: false, url: '' },
Expand Down Expand Up @@ -201,8 +201,7 @@ const routes: RouteEntry[] = [
{ match: (u) => u === '/api/setup/status', respond: () => STATUS },
{ match: (u) => u.startsWith('/api/setup/config'), respond: (_, m) => m === 'POST' ? { status: 'ok' } : CONFIG },
{ match: (u) => u.startsWith('/api/setup/azure/login'), respond: () => ({ status: 'ok' }) },
{ match: (u) => u.startsWith('/api/setup/copilot/login'), respond: () => ({ status: 'ok', user_code: 'ABC-123', verification_uri: 'https://github.com/login/device' }) },
{ match: (u) => u.startsWith('/api/setup/copilot/status'), respond: () => ({ authenticated: true }) },

{ match: (u) => u.startsWith('/api/setup/configuration/save'), respond: () => ({ status: 'ok' }) },
{ match: (u) => u.startsWith('/api/setup/tunnel/start'), respond: () => ({ status: 'ok' }) },
{ match: (u) => u.startsWith('/api/setup/channels/'), respond: () => ({ status: 'ok' }) },
Expand Down
Loading
Loading