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
2,126 changes: 1,855 additions & 271 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions charts/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,13 @@ kubectl -n agents get deploy,svc,pods

kubectl -n agents port-forward svc/agents 8080:80
curl -sf http://127.0.0.1:8080/health
curl -sf http://127.0.0.1:8080/ready
```

The same HTTP Service exposes the TanStack Start control-plane UI at `/`, existing `/v1/*` APIs, and the `/mcp`
endpoint. If you expose `svc/agents` through an ingress or Tailscale Service, route all three surfaces to the
same HTTP port.

Run the chart smoke examples from the published package:

```bash
Expand Down Expand Up @@ -186,6 +191,7 @@ You do not need to understand every CRD before installing the chart. These are t
The chart installs:

- Agents control-plane `Deployment` and HTTP `Service`
- TanStack Start control-plane UI served by the same HTTP `Service` as `/v1/*` and `/mcp`
- Optional gRPC `Service`
- Optional controllers deployment for reconciliation/runtime work
- Optional metrics `Service`, `ServiceMonitor`, and Grafana dashboard ConfigMap
Expand Down
1 change: 1 addition & 0 deletions docs/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ clear entrypoints, clear “source of truth”, and a complete catalog of relate
- Implementing the Helm chart and controllers (implementation-grade): `agents-helm-chart-implementation.md`
- Chart intent and scope (high-level design): `agents-helm-chart-design.md`
- Creating AgentRuns safely (prompt precedence): `agentrun-creation-guide.md`
- Agents control-plane UI and schema-form rollout proof: `control-plane-ui.md`
- Connecting Codex to Agents through MCP: `codex-mcp-agents.md`
- Launching workflow loops correctly (state reuse + checks): `agentrun-workflow-loop-launch-guide.md`
- Running Codex Spark review/fix PR cycles: `codex-spark-review-cycle.md`
Expand Down
63 changes: 63 additions & 0 deletions docs/agents/control-plane-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Agents Control Plane UI

Status: Current (2026-05-25)

`services/agents` serves the operator UI and the existing API surface from the same control-plane service. The service exposes:

- `/` and `/primitives/**` for the TanStack Start UI
- `/health` and `/ready` for probes
- `/v1/*` for existing typed resources, logs, terminal events, and AgentRun submission
- `/mcp` for the Agents MCP endpoint

## Architecture

The UI is a TanStack Start application running on Bun with Vite 8, React 19, Tailwind CSS v4, shadcn components, and `@tailwindcss/vite`.

The frontend route tree lives in `services/agents/src/app-routes`. Existing controller, reconciler, gRPC, runner, and `/v1` implementation modules remain separate from the frontend bundle. TanStack Start owns the web entrypoint, while Start server routes delegate `/v1/*`, `/mcp`, `/health`, `/ready`, and `/metrics` to the existing Agents runtime handlers.

The control-plane image builds the Start `.output` bundle and starts it with `bun run start`. The controller image still starts `src/server/controller-entrypoint.ts`.

## CRD Schema Forms

The primitive registry is generated from `charts/agents/crds/*.yaml` by:

```bash
bun run --cwd services/agents generate:control-plane-registry
```

The generated registry contains each CRD kind, group, version, plural, scope, `openAPIV3Schema`, and display metadata. The schema form reads the CRD schema and renders native React Hook Form controls for strings, numbers, booleans, enums, arrays, and nested objects. Schemaless or preserved-unknown object subtrees use a JSON/YAML textarea fallback. Kubernetes remains the authoritative validator on submit.

## Primitive Routes

- `/primitives`: overview of every chart primitive
- `/primitives/$kind`: resource list for one primitive
- `/primitives/$kind/$namespace/$name`: resource detail
- `/primitives/$kind/new`: schema-form create flow

Navigation uses header breadcrumbs. There is no explicit back button.

## Create Flow

Create screens build a raw manifest preview before submit. Submission goes through the UI server endpoint backed by the existing typed-resource apply behavior and includes an `Idempotency-Key`.

For `AgentRun`, guided creation keeps `spec.parameters.prompt` out of the submitted manifest when `spec.implementationSpecRef` is present, preserving the ImplementationSpec text as the source of truth.

## Smoke And Rollout Proof

Local validation before a PR:

```bash
bun run --cwd services/agents test
bun run --cwd services/agents tsc
bun run --cwd services/agents lint
scripts/agents/validate-agents.sh
bun run lint:argocd
```

Render the GitOps app before merging:

```bash
mise exec helm@3 -- kustomize build --enable-helm argocd/applications/agents >/tmp/agents-render.yaml
```

After merge, let Argo CD sync the enabled `agents` app. Live proof must include health, ready, `/v1/control-plane/status`, `/mcp`, UI navigation across primitive categories, one disposable non-executing primitive created through the schema form, and a deterministic fake app-server smoke `AgentRun` with `Succeeded=True` and logs available through `/v1/control-plane/logs?tailLines=5`.
4 changes: 4 additions & 0 deletions docs/agents/primitives/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ These documents describe:
Agents remains the control plane for every primitive documented here. External callers should interact with
Agents APIs; Jangar consumes those APIs for domain-specific surfaces.

The TanStack Start operator UI for listing, inspecting, and creating these primitives is documented in
[`../control-plane-ui.md`](../control-plane-ui.md).

## Documents

- `agent.md`
- `memory.md`
- `orchestration.md`
- `supporting-primitives.md`
- `../control-plane-ui.md`

Jangar-specific primitive consumers remain under `docs/jangar/primitives/`:

Expand Down
7 changes: 7 additions & 0 deletions packages/scripts/src/agents/__tests__/smoke-agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,13 @@ const objectAt = (value: unknown, key: string) =>
value && typeof value === 'object' ? ((value as Record<string, unknown>)[key] as unknown) : undefined

describe('scheduled AgentRun templates', () => {
it('enables the CI controller deployment so workflow smoke AgentRuns are reconciled', () => {
const values = readYamlObjects('scripts/agents/values-ci.yaml')[0]
const controllers = objectAt(values, 'controllers')

expect(objectAt(controllers, 'enabled')).toBe(true)
})

it('requires every checked-in AgentProvider fixture to declare a normalized adapter', () => {
const agentProviderFiles = [
'argocd/applications/agents/agents-primitives-agentprovider.yaml',
Expand Down
7 changes: 7 additions & 0 deletions packages/scripts/src/agents/smoke-agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,12 @@ const waitForDeploymentRollout = async (namespace: string, deployment: string, t
}
}

const waitForOptionalDeploymentRollout = async (namespace: string, deployment: string, timeoutFlag: string) => {
const exists = await execCapture(['kubectl', '-n', namespace, 'get', 'deployment', deployment, '-o', 'name'])
if (exists.exitCode !== 0) return
await waitForDeploymentRollout(namespace, deployment, timeoutFlag)
}

const runDiagnosticsCommand = async (cmd: string[]) => {
const exitCode = await runInherit(cmd)
if (exitCode !== 0) {
Expand Down Expand Up @@ -849,6 +855,7 @@ const main = async () => {

await run('helm', helmArgs)
await waitForDeploymentRollout(namespace, releaseName, timeoutFlag)
await waitForOptionalDeploymentRollout(namespace, `${releaseName}-controllers`, timeoutFlag)

await run(
'kubectl',
Expand Down
1 change: 1 addition & 0 deletions scripts/agents/values-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ image:
pullPolicy: IfNotPresent

controllers:
enabled: true
image:
repository: registry.ide-newton.ts.net/lab/agents-controller

Expand Down
4 changes: 3 additions & 1 deletion services/agents/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ ARG AGENTS_BUILD_CI=false
ENV NODE_OPTIONS=${AGENTS_BUILD_NODE_OPTIONS}
ENV CI=${AGENTS_BUILD_CI}
RUN bun run tsc
RUN bun run build

FROM agents-tools AS agents-deps-prod
WORKDIR /app
Expand Down Expand Up @@ -187,6 +188,7 @@ RUN mkdir -p /app/packages/agent-contracts/node_modules \
&& ln -sf /app/node_modules/effect /app/packages/agent-contracts/node_modules/effect
WORKDIR /app/services/agents
COPY --from=agents-build /app/services/agents/package.json ./package.json
COPY --from=agents-build /app/services/agents/.output ./.output
COPY --from=agents-build /app/services/agents/src ./src
COPY --from=agents-build /app/services/agents/scripts ./scripts

Expand All @@ -202,7 +204,7 @@ ENV AGENTS_CONTROLLER_ENABLED=0 \
ENV AGENTS_VERSION=${AGENTS_VERSION} \
AGENTS_COMMIT=${AGENTS_COMMIT}

CMD ["bun", "run", "src/server/index.ts"]
CMD ["bun", "run", "start"]

FROM ${BUN_BASE_IMAGE}:${BUN_VERSION} AS controller
ARG AGENTS_VERSION=""
Expand Down
25 changes: 25 additions & 0 deletions services/agents/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/app.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
31 changes: 29 additions & 2 deletions services/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@
"private": true,
"type": "module",
"scripts": {
"generate:control-plane-registry": "bun run scripts/generate-control-plane-registry.ts && bunx oxfmt src/control-plane/primitive-registry.generated.ts",
"dev": "bun run generate:control-plane-registry && vite dev",
"build": "bun run generate:control-plane-registry && vite build",
"start": "bun .output/server/index.mjs",
"start:legacy": "bun run src/server/index.ts",
"test": "bunx vitest run --config vitest.config.ts",
"tsc": "bunx tsc --noEmit -p tsconfig.json",
"tsc": "bun run generate:control-plane-registry && bunx tsc --noEmit -p tsconfig.json",
"lint": "bunx oxfmt --check src scripts",
"lint:oxlint": "oxlint --config ../../.oxlintrc.json .",
"lint:oxlint:type": "oxlint --config ../../.oxlintrc.json --type-aware --tsconfig ./tsconfig.json ."
Expand All @@ -19,19 +24,41 @@
"@proompteng/codex": "workspace:*",
"@proompteng/otel": "workspace:*",
"@proompteng/temporal-bun-sdk": "workspace:*",
"@tanstack/react-router": "1.170.8",
"@tanstack/react-start": "1.168.13",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"crossws": "^0.4.5",
"effect": "3.21.2",
"h3": "2.0.1-rc.22",
"kysely": "0.28.17",
"lucide-react": "^1.16.0",
"nats": "^2.29.3",
"nitro": "^3.0.260522-beta",
"pg": "8.20.0",
"xstate": "5.31.0"
"radix-ui": "^1.4.3",
"react": "19.2.6",
"react-dom": "19.2.6",
"react-hook-form": "^7.76.1",
"tailwind-merge": "^3.6.0",
"xstate": "5.31.0",
"yaml": "^2.9.0"
},
"devDependencies": {
"@fontsource-variable/geist": "^5.2.9",
"@tailwindcss/vite": "4.3.0",
"@types/node": "25.6.0",
"@types/pg": "8.20.0",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"bun-types": "1.3.14",
"nitro": "^3.0.260522-beta",
"shadcn": "^4.8.0",
"tailwindcss": "4.3.0",
"tw-animate-css": "^1.4.0",
"typescript": "^6.0.3",
"vite": "8.0.14",
"vitest": "4.1.5"
},
"packageManager": "bun@1.3.14"
Expand Down
32 changes: 32 additions & 0 deletions services/agents/scripts/generate-control-plane-registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { tmpdir } from 'node:os'

import { describe, expect, it } from 'vitest'

import { controlPlanePrimitiveKinds } from '../src/control-plane/primitive-registry.generated'
import { listPrimitiveKinds } from '../src/server/control-plane-primitive-kinds'
import { loadPrimitiveRegistryEntries, resolvePrimitiveRegistryEntries } from './generate-control-plane-registry'

describe('control-plane primitive registry generation', () => {
it('extracts every chart CRD and matches control-plane primitive kinds', async () => {
const entries = await loadPrimitiveRegistryEntries()
const kinds = entries.map((entry) => entry.kind).sort()
const expectedKinds = listPrimitiveKinds({ includeSwarm: true }).sort()

expect(kinds).toEqual(expectedKinds)
expect(controlPlanePrimitiveKinds.slice().sort()).toEqual(expectedKinds)
})

it('allows pruned Docker contexts to use the committed generated registry', async () => {
const directory = await mkdtemp(join(tmpdir(), 'agents-control-plane-registry-'))
const generatedFile = join(directory, 'primitive-registry.generated.ts')
await writeFile(generatedFile, 'export const controlPlanePrimitiveRegistry = []\n')

try {
await expect(resolvePrimitiveRegistryEntries(join(directory, 'missing-crds'), generatedFile)).resolves.toBeNull()
} finally {
await rm(directory, { force: true, recursive: true })
}
})
})
Loading
Loading