Skip to content

Switch to agent-server#2

Merged
neuromaxer merged 27 commits into
mainfrom
codex/pi-harness-distribution
Jun 9, 2026
Merged

Switch to agent-server#2
neuromaxer merged 27 commits into
mainfrom
codex/pi-harness-distribution

Conversation

@Canvinus

@Canvinus Canvinus commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Switches Appx's default agent path from legacy OpenCode to Pi through the separate agent-server service.

  • Adds Pi tooling/install/bootstrap support, version pinning, systemd wiring, and verification updates.
  • Proxies project-scoped Appx agent routes to agent-server with trusted project id/name/directory headers.
  • Scaffolds each project with first-party .pi harness files: prompt, guardrails extension, settings, and egress skill helper.
  • Adds the Pi Agent tab frontend with project sessions, provider-neutral SSE streaming, model selection, thinking level controls, stop/send behavior, and tool call rendering.
  • Adds Settings UI for Pi provider credentials, subscription login flows, custom providers such as LiteLLM, and legacy OpenCode fallback mode.
  • Updates README docs for Pi deployment, credentials, project-local harnesses, and the unified streaming contract.

Why

The old Appx flow was tied to one OpenCode backend. Pi gives Appx provider-flexible model selection, subscription auth, custom provider support, and project-local extensions, but Appx still needs a single browser contract and isolated sessions per project.

Validation

  • npm exec -- tsc -b passed locally.
  • VM task build passed.
  • VM task test passed.
  • VM appx.service active after installing the rebuilt binary.
  • VM dashboard smoke check returned HTTP 200 using the configured sslip host header.
  • Direct project isolation smoke test confirmed sessions created in one project directory did not appear in another.

Known Follow-Up

  • task lint still reports pre-existing React hook lint errors in EgressRequestDock.tsx and OpenCodeStatus.tsx; this PR does not change those files' hook patterns.

Dependency

Pairs with appx-org/agent-server#1, which provides project-scoped Pi runtimes and provider/model APIs.

@Canvinus Canvinus marked this pull request as ready for review May 23, 2026 20:00
@Canvinus

Copy link
Copy Markdown
Collaborator Author

@codex

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

Copy link
Copy Markdown

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: 6aa47e1fed

ℹ️ 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".

Comment thread web/src/api/piAgent.ts Outdated
Comment on lines +62 to +64
if (res.status === 401) {
window.location.href = '/login';
throw new Error('Unauthorized');

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 Gate login redirect on Appx auth header

This new Pi API client redirects to /login on every HTTP 401, but /api/agent/* responses can also return 401 from the upstream agent backend (for example, backend token mismatch or provider-side auth errors) while the Appx session is still valid. Because the redirect ignores X-Appx-Auth: required, users get logged out for non-session failures and can enter a misleading re-login loop instead of seeing the actual agent error.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sounds like a valid concern

Comment thread deploy/bootstrap.sh
APPX_HOST=$APPX_HOST
APPX_DATA=$APPX_DATA
APPX_PORT=$APPX_PORT
APPX_AGENT_SERVER_URL=http://127.0.0.1:4001

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 Persist configured agent-server URL in bootstrap env

The bootstrap template always writes APPX_AGENT_SERVER_URL=http://127.0.0.1:4001, which overrides any non-default value a deployer intended for first-time setup. In installations where agent-server runs on a different host/port, bootstrap completes with an unusable proxy target until the env file is manually corrected, so the generated config should honor the chosen/runtime value instead of hardcoding the default.

Useful? React with 👍 / 👎.

Comment thread deploy/system-setup.sh
Comment on lines +109 to +110
if [ ! -d /home/appx-agent/.pi ] && [ -d /home/opencode/.pi ]; then
cp -a /home/opencode/.pi /home/appx-agent/.pi

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Leftover mention of opencode in [ -d /home/opencode/.pi ] and cp -a /home/opencode/.pi

Comment thread deploy/system-setup.sh
Comment on lines +145 to +146
if id -u opencode >/dev/null 2>&1; then
pkill -u opencode -f '(^|/)agent-server( |$)|agent-server/dist/server\.js' 2>/dev/null || true

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

some more opencode leftovers

Comment thread deploy/tools-install.sh
Comment on lines +148 to +164
AGENT_SERVER_DIR="${AGENT_SERVER_DIR:-}"
if [ -z "$AGENT_SERVER_DIR" ] && [ -d "$REPO_DIR/../agent-server" ]; then
AGENT_SERVER_DIR="$(cd "$REPO_DIR/../agent-server" && pwd)"
fi

if [ -n "$OPENCODE_NPM_VERSION" ] && [ "$CURRENT" = "$OPENCODE_NPM_VERSION" ]; then
echo "opencode already at $OPENCODE_NPM_VERSION"
if [ -n "$AGENT_SERVER_DIR" ] && [ -f "$AGENT_SERVER_DIR/package.json" ]; then
echo "installing agent-server from $AGENT_SERVER_DIR..."
(
cd "$AGENT_SERVER_DIR"
npm ci
npm run build
npm install -g .
)
ln -sf "$NODE_BIN_DIR/agent-server" /usr/local/bin/agent-server
echo "agent-server installed: /usr/local/bin/agent-server"
else
echo "installing opencode${OPENCODE_NPM_VERSION:+ $OPENCODE_NPM_VERSION} via npm..."
npm install -g "opencode-ai@${OPENCODE_NPM_VERSION:-latest}"
ln -sf "$NODE_BIN_DIR/opencode" /usr/local/bin/opencode
echo "opencode installed: $(/usr/local/bin/opencode --version 2>/dev/null)"
echo "agent-server repo not found; clone appx-org/agent-server next to appx or set AGENT_SERVER_DIR"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

That's obviously brittle. Long term want to get it from npm registry

@@ -1,3 +1,2 @@
DROP INDEX IF EXISTS idx_assigned_port_unique;
ALTER TABLE projects DROP COLUMN assigned_port;
ALTER TABLE projects DROP COLUMN opencode_project_id;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why we drop the opencode column without adding pi column? Where do we get info from?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Cool idea! Really like how we can reduce attack surface by leveraging pi extensions

Comment thread internal/project/templates/pi/AGENTS.md Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We'll extend this template with references and existing app examples in the future

@neuromaxer

Copy link
Copy Markdown
Collaborator

Is there now just one Pi server for all apps created from agent-server? @codex

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Comment thread internal/project/pi_harness.go Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What does this file do?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not really sure why we need it if agent-server is always in loopback (on same server as all apps)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is reverse proxy to route calls made on appx UI to agent-server backend

Comment thread internal/server/router.go
Comment on lines -52 to -54
api.HandleFunc("GET /api/settings/api-key", handleGetAPIKeyStatus(a.Store))
api.HandleFunc("PUT /api/settings/api-key", handleSetAPIKey(a.Store, oc))
api.HandleFunc("DELETE /api/settings/api-key", handleDeleteAPIKey(a.Store, oc))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How do we handle API key in pi now?

Comment thread internal/server/router.go
}
agentServerURL := rcfg.AgentServerURL
if agentServerURL == "" {
agentServerURL = "http://127.0.0.1:4001"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hardcoded - want to pass by env var

Comment thread internal/server/router.go
Comment on lines +66 to +74
api.Handle("GET /api/agent/{agentPath...}", agentGlobalProxy)
api.Handle("POST /api/agent/{agentPath...}", agentGlobalProxy)
api.Handle("PUT /api/agent/{agentPath...}", agentGlobalProxy)
api.Handle("DELETE /api/agent/{agentPath...}", agentGlobalProxy)
agentProxy := agentServerProxyHandler(pm, agentServerURL, rcfg.AgentServerToken)
api.Handle("GET /api/projects/{id}/agent/{agentPath...}", agentProxy)
api.Handle("POST /api/projects/{id}/agent/{agentPath...}", agentProxy)
api.Handle("PATCH /api/projects/{id}/agent/{agentPath...}", agentProxy)
api.Handle("DELETE /api/projects/{id}/agent/{agentPath...}", agentProxy)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are those APIs mirroring agent-server?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

yes, for reverse proxy

Comment thread internal/server/router.go

// OpenCode health endpoint — registered on the outer mux before the /api/opencode/
// proxy so the more-specific pattern takes precedence. Protected by auth middleware.
mux.Handle("GET /api/opencode/health", a.Middleware(http.HandlerFunc(handleOpenCodeHealth(oc))))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should prly have an endpoint for health of agent-server in case it gets stuck without failing and systemd doesn't restart it (there are no healthchecks in systemd afaik)

Comment thread web/src/api/client.ts
Comment on lines +90 to +102
export interface AgentAuthProvider {
provider: string;
name: string;
configured: boolean;
credentialType?: 'api_key' | 'oauth';
source?: 'stored' | 'runtime' | 'environment' | 'fallback' | 'models_json_key' | 'models_json_command';
label?: string;
supportsApiKey: boolean;
supportsSubscription: boolean;
modelCount: number;
availableModelCount: number;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

All of those interfaces and agent-server UI in general should probably live in a dedicated repo (as part of agent-server?)

Comment thread web/src/api/piAgent.ts Outdated
Comment on lines +1 to +21
export type PiSessionInfo = {
id: string;
createdAt: string;
firstMessage: string;
messageCount: number;
};

export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';

export type PiAgentModel = {
provider: string;
id: string;
name: string;
api: string;
reasoning: boolean;
available: boolean;
input: Array<'text' | 'image'>;
contextWindow: number;
maxTokens: number;
defaultThinkingLevel?: ThinkingLevel;
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similar comment - it's best if backend and frontend for interacting with agents live in one place (e.g. agent-server) rather than be separated in two different repos. It's harder to keep them in sync in two different repos

Comment thread web/src/lib/pi-agent/reducer.ts Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't think we have anything like that in any of the apps so far. I'm not sure but it looks like it's stepping into agent-server area of responsibility

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It turns SSE stream from agent-server into renderable UiMessage

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

That got quite bigger. Need to investigate

Comment thread deploy/tools-install.sh
CURRENT=$(/usr/local/bin/opencode --version 2>/dev/null || echo "")
AGENT_SERVER_DIR="${AGENT_SERVER_DIR:-}"
if [ -z "$AGENT_SERVER_DIR" ] && [ -d "$REPO_DIR/../agent-server" ]; then
AGENT_SERVER_DIR="$(cd "$REPO_DIR/../agent-server" && pwd)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would actually pass agent-server dir via environment variables similarly to how we pass some other vars (I'll look for reference)

Comment on lines +38 to +45
const thinkingLabels: Record<ThinkingLevel, string> = {
off: 'Off',
minimal: 'Minimal',
low: 'Low',
medium: 'Medium',
high: 'High',
xhigh: 'X-high',
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

those are model-specific

Comment on lines +374 to +381
const styles: Record<string, React.CSSProperties> = {
container: {
flex: 1,
display: 'flex',
flexDirection: 'column',
minHeight: 0,
overflow: 'hidden',
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We should use tailwind css

Comment thread internal/server/agent_proxy.go Outdated
Comment on lines +17 to +19
agentProjectIDHeader = "X-Appx-Project-Id"
agentProjectNameHeader = "X-Appx-Project-Name"
agentProjectDirHeader = "X-Appx-Project-Dir"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Probably need only "X-Appx-Project-Id" header, others are kind of redundant and should be passed once when creating the project

@neuromaxer neuromaxer changed the title [codex] Add Pi agent backend and UI Switch to agent-server Jun 9, 2026
@neuromaxer neuromaxer merged commit 886380f into main Jun 9, 2026
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.

2 participants