Skip to content

feat(registration): self-register FuzeAgent with the FuzeFront portal - #140

Merged
izzywdev merged 5 commits into
mainfrom
claude/fuze-fuzefront-registration-hymmqk
Jul 29, 2026
Merged

feat(registration): self-register FuzeAgent with the FuzeFront portal#140
izzywdev merged 5 commits into
mainfrom
claude/fuze-fuzefront-registration-hymmqk

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

FuzeAgent's portal presence is currently a hardcoded entry in FuzeFront's builtins.ts — FuzeFront's source has to change for FuzeAgent's own menu entry to change. This moves that declaration into this repo, where it belongs.

Platform side: FuzeFront#426 (merged).

Module Federation is real here

Unlike most siblings (which register as iframe because they expose no remoteEntry), integration.type is genuinely module-federation. The contract is copied from services/ui-react/vite.config.ts and verified to match:

Field Value Source in vite.config.ts
scope fuzeagentApp federation name
module ./FuzeAgentApp exposes key
remoteEntry https://fuzeagent.prod.fuzefront.com/remoteEntry.js filename + host

Menu placement

"nav": { "section": "build", "order": 10 }

FuzeAgent leads the build stage. Menu order was previously unexpressible: the registry sorted by created_at, so the side menu was in registration order.

Policy

Derived from services/orchestrator/models.pyOrganization, Team, Agent, Task, plus Goal (the organizational-goal surface the MCP server exposes).

Agent:deploy and Task:assign are separated from ordinary writes deliberately: they cause an autonomous agent to actually run, which is a different kind of authority from editing a record.

MCP is declared enabled: true — because it's already real

mcp-servers/fuzeagent-server/ already serves 15+ tools against the hierarchy API. This is the only repo in the rollout where the MCP block isn't a scaffold.

Outstanding, and flagged rather than invented: there's no tools.json declaring mutates per tool. These mutate and must be mutates: true:

assign_task · deploy_agent · create_custom_agent · update_task_status · create_organizational_goal · update_goal_progress · create_goal_conversation

The list_* / get_* tools are mutates: false. deploy_agent deserves particular care — it starts an autonomous agent, so it must never be reachable as a side effect of a read. Classification is mcp-maintainer's to reconcile.

A2A block names the real serving role

agent-templates/a2a/ implements card generation, adapter, authz and identity, and agent-templates/roles/agent-orchestrator/role.json is a fully-described serving role. The manifest names agent-orchestrator accurately — my first draft had guessed agent-platform, which would have been wrong.

Left enabled: false on purpose: flipping it requires verifying the card projects schema-valid against the frozen contract — a2a-maintainer's call, not something a manifest edit should assert.

🚨 NOT DONE — init container not wired

deploy/helm/fuzeagent/ exists, but this is a multi-service deployment and exactly one deployment must run registration. Wiring more than one would have them race and duplicate-register on every rollout. Choosing the owner is devops-engineer's call, flagged rather than guessed — steps are in registration/README.md.

Follow-up

Once this is live, the hardcoded fuzeagent entry in FuzeFront's builtins.ts can be retired — deliberately last, and behind a flag, so the app never vanishes from the menu in the gap.

Notes

  • This PR was auto-opened as a draft by the repo's open-draft-pr workflow; marked ready for review, since per CLAUDE.md agent PRs are non-draft + auto-merge and drafts are only legitimate when labelled wip/hold/blocked.
  • Auto Merge PR will fail until FuzeSDLC#66 lands — the canonical auto-merge.yml template has no permissions block, so gh pr merge --auto hits Resource not accessible by integration in every consuming repo.

FuzeAgent's portal presence is currently a HARDCODED entry in FuzeFront's
backend/applications/src/app-registry/builtins.ts — FuzeFront's source has to change
for FuzeAgent's own menu entry to change. This moves that declaration here.

- registration/manifest.json — identity + `nav: { section: "build", order: 10 }`,
  leading the build stage. Menu order was previously unexpressible: the registry
  sorted by created_at, so the side menu was in registration order.

  integration.type is genuinely `module-federation` here, unlike most siblings. The
  contract is copied from services/ui-react/vite.config.ts and verified to match:
  scope=fuzeagentApp, module=./FuzeAgentApp, remoteEntry at the prod host.

- registration/policy.json — derived from services/orchestrator/models.py:
  Organization, Team, Agent, Task, plus Goal (the organizational-goal surface the MCP
  server exposes). Agent:deploy and Task:assign are split from ordinary writes because
  they cause an autonomous agent to actually RUN — a different kind of authority from
  editing a record.

- .fuze/manifest.json:
  * mcp block with enabled:TRUE — this is not a scaffold. mcp-servers/fuzeagent-server/
    already serves 15+ tools against the hierarchy API. The note names the tools that
    MUST be classified mutates:true (assign_task, deploy_agent, create_custom_agent,
    update_task_status, create_organizational_goal, update_goal_progress,
    create_goal_conversation) and flags the missing tools.json for mcp-maintainer.
  * a2a block naming the REAL serving role `agent-orchestrator` — agent-templates/a2a/
    implements card_generator/adapter/authz/identity and the role is fully described.
    Left enabled:false because flipping it requires verifying the card projects
    schema-valid, which is a2a-maintainer's call, not a manifest edit's.

Validated: manifest passes the generated AppManifest schema; the MF scope/module match
vite.config.ts exactly; every permission resolves to a resource+action the same
document declares.

NOT DONE, deliberately: the init container is not wired. deploy/helm/fuzeagent exists
but this is a multi-service deployment and exactly one deployment must register —
wiring more than one would have them race and duplicate-register. Picking the owner is
devops-engineer's call, flagged not guessed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv
@izzywdev
izzywdev marked this pull request as ready for review July 28, 2026 11:05
@izzywdev
izzywdev self-requested a review as a code owner July 28, 2026 11:05
@izzywdev izzywdev added the auto-merge label Jul 28, 2026 — with Claude
izzywdev and others added 2 commits July 29, 2026 09:32
…rmissions

Two red checks with the same root shape: something was DECLARED without the file
that backs it, and neither gate could self-heal.

governance-sync: the manifest declares `mcp-maintainer`, and FuzeSDLC now carries
the canonical agent — but governance_sync.py only COPIES a file that already exists
and has drifted. A wholly absent one is reported `missing` and hard-fails
(governance_sync.py:84), so `--write` was never going to create it. Added
.claude/agents/mcp-maintainer.md from the canonical, plus the mcp-maintain.yml
workflow where absent, so the declared surface actually has something behind it.

auto-merge.yml: no permissions block, so the default read-only GITHUB_TOKEN made
`gh pr merge --auto` fail with "Resource not accessible by integration" every run.
FuzeSDLC#66 fixed the canonical template; this copy was stale. Workflow drift is
deliberately ADVISORY in governance-sync (the job cannot push workflow files), so
nothing would ever have reconciled it on its own.

Verified with governance_sync.py against the canonical: ok=True, missing=[].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv
@github-actions
github-actions Bot enabled auto-merge (squash) July 29, 2026 09:34
izzywdev and others added 2 commits July 29, 2026 14:15
No repo in the family declared `mobile` at all, so `mobile-app-engineer` had nothing
to read on merge and no gate could tell "mobile is out of scope" from "nobody wrote
it down".

Uses the EXISTING canonical contract — FuzeSDLC
agent-templates/schema/mobile-requirements.schema.json — which already sanctions a
`mobile` block inside .fuze/manifest.json. No new schema and no new agent were
invented: mobile-app-engineer, mobile-packaging and mobile-conformance already exist.

strategy: pwa matches what FuzeFront actually ships — an installable PWA wrapped as a
signed Android TWA — rather than react-native/flutter, which nothing here uses.
targets omit ios until there is a signing identity for it.

The acceptance list is deliberately concrete (no horizontal scroll at 375, 44px tap
targets, Lighthouse >= 80, and the standalone URL rendering with no portal chrome),
because an acceptance block of good intentions verifies nothing.

Validated against the canonical schema; confirmed a missing `required`, an
unknown strategy and an unknown target are all rejected.

NOTE ON SEQUENCING: this declares mobile INTENT only. The `modes: ["portal",
"standalone"]` + `routing.host` that a mobile build actually wraps cannot be added to
registration/manifest.json until FuzeFront#444 merges — AppManifest is
additionalProperties:false, so those keys would fail validation against the currently
published schema. Registration side follows once that lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv
This repo declares a `mobile` block, and a mobile build can only wrap a
URL that stands on its own — an app store has nothing to point at
otherwise. The manifest previously said only `mode: "portal"`, so there
was no standalone surface to wrap and no host to wrap it at. That made
the mobile contract unsatisfiable on its face.

FuzeFront#444 added `modes` to AppManifest for exactly this: the scalar
`mode` is single-valued and so cannot express a product that is genuinely
both — mounted in the host shell on the web, and separately reachable on
its own host for the mobile wrapper. `modes` is the multi-valued form and
wins where both are present.

- `modes: ["portal", "standalone"]` — portal stays the default surface.
  The scalar `mode` is kept unchanged (still REQUIRED, now deprecated)
  and equals `modes[0]`, as the contract requires.
- `routing.host` — the user-facing standalone host, `<slug>.fuzefront.com`.
  Distinct from `*.prod.fuzefront.com`, which is the infra host serving
  the portal `remoteEntry`. Portal integration is untouched.

Validated against packages/onboarding-kit/manifest.schema.json at merged
master (9d2b0af).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GaPa3JgrVNtWrGvqQEAEqv

- name: Checkout PR head (writable, same-repo only)
if: steps.guard.outputs.ok == 'true'
uses: actions/checkout@v4
@izzywdev
izzywdev merged commit 22097f1 into main Jul 29, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants