Skip to content

Platform architecture

Doc type: Explanation  ·  Scope: Harne8 platform (this repository) Verified: 2026-07-19, against docker-compose.prod.yaml and the ratified POSE↔Harne8 boundary (spec pose-harne8-control-plane-integration, delivered in pose-dist).

This page describes how the platform components compose. For the governance engine itself (specs, gates, evidence, validation matrix, MCP tools) see POSE's own architecture doc — it is not duplicated here.

Responsibility boundary

Repository authority never moves out of POSE. Every other component composes around it without becoming a second lifecycle authority:

Component Responsibility Consumes / exposes
POSE Governs — owns specs, gates, evidence, policy input CLI, MCP tools (pose-mcp)
Conductor Orchestrates idempotent governed runs with approvals conductor_run_*, pose_validate_request/approve/submit/status/cancel
Harness Executes an approved, digest-pinned plan Submit API, reconciled back via pose reconcile-evidence
GraphForge Contextualizes — semantic code graph Consumes pose portfolio-projection and pose semantic-suggest
Portal Presents — portfolio, review UI Consumes policy-filtered projections; never a second lifecycle authority

Every one of these boundaries is independently testable without the others present: POSE degrades to a clear configuration error (not a crash) when a Harne8 component isn't wired, proven by an executable test in pose-dist rather than asserted in prose. No open-core command requires CONDUCTOR_URL, POSE_MCP_IDENTITY_SECRET, HARNE8_PROJECTS_DIR or any other Harne8-specific environment variable — every one is optional.

Deployed service topology

The production compose file (docker-compose.prod.yaml) wires these services. This is infrastructure grounding, not a substitute for reading the compose file itself when making changes to it.

flowchart TB
    subgraph edge["Edge"]
        traefik[Traefik]
    end

    subgraph conductor_plane["Conductor — control plane"]
        temporal[Temporal + Temporal UI]
        capi[conductor-api]
        cworker[conductor-worker]
        cmig[conductor-migrate]
    end

    subgraph graphforge_plane["GraphForge — context plane"]
        ast[ast-engine]
        gitidx[git-indexer]
        infraidx[infra-indexer]
        rtcol[runtime-collector]
        sem[semantic-enricher]
        gwriter[graph-writer]
        planner[planning-engine]
        gmcp[mcp-server]
        neo4j[(Neo4j)]
    end

    subgraph governance["Governance"]
        posemcp[pose-mcp]
        enforce[mcp-enforce-sidecar]
    end

    subgraph portal_plane["Portal"]
        gweb[graphforge-web]
    end

    subgraph shared["Shared infra"]
        kafka[(Kafka + Zookeeper)]
        pg[(Postgres)]
        redis[(Redis)]
    end

    traefik --> capi
    traefik --> gweb
    capi --> temporal
    cworker --> temporal
    cworker --> posemcp
    cworker --> enforce
    posemcp -.policy decision.-> enforce
    ast --> kafka --> gwriter --> neo4j
    gmcp --> neo4j
    gweb --> capi
    gweb --> gmcp
    capi --> pg
    cworker --> redis

pose-mcp and mcp-enforce are vendored, not forked. Both directories at this repository's root are a git submodule of pose-dist (pose-dist/pose-mcp, pose-dist/mcp-enforce) — the same code that ships as the standalone POSE product. The Docker build context for pose-mcp resolves the sibling mcp-enforce module through the submodule tree (replace github.com/harne8/mcp-enforce => ../mcp-enforce in pose-mcp/go.mod), so there is exactly one source of truth: fixing a bug or shipping a feature in pose-dist and bumping the submodule pointer here is the entire update path — no manual copy, no divergence.

Where decisions are recorded

Structural or public-contract changes get an ADR before merge (.pose/adr/). Recent examples grounding this page:

For the full architectural narrative and vision, see docs/PROPOSTA-SOLUCAO-FINAL.md.

Conductor's workflow model

Two Temporal workflows own the operational lifecycle (conductor/README.md):

  • AgentRunWorkflow (task queue conductor-v1): LoadContextPackageRegisterClaim (task CLAIMEDEXECUTING) → CreateAgentRunExecuteHarness (a failed validator/budget/policy check is not an infra error — it becomes a FAILED run with evidence, never a silent retry) → PersistRunResultsFinalizeRun (SUCCEEDED/FAILED; success moves the task to WAITING_HUMAN, the mandatory human review gate for the next cut).
  • TaskOrchestrationWorkflow owns task state end to end: claim by signal, up to N attempts (each producing a fresh, checksummed Context Package), success → WAITING_HUMAN with an approval SLA (decision always attributed to a principal via ApprovalDecidedit never auto-approves; an expired SLA escalates, an expired escalation window blocks the task), approved → CreatePullRequest (no creator configured or an external error moves to WAITING_TOOL rather than fabricating a PR).

Every event flows through OutboxDispatchWorkflow: unpublished events are validated against their schema, projected into the Portal timeline, and selectively turned into GraphForge mutations (task.*Task node, agent.run.*AgentRun node + EXECUTES edge, approval.*Approval node + GATES edge). A failed GraphForge bridge never blocks the Conductor or the timeline — it records last_error and keeps going.

Harness's execution contract

Harness is stateless and consumes contracts/schemas/v1/context-package.schema.json (harness/README.md). Before any sandbox is prepared, it validates execution_identity: a valid issued_at/expires_at window, a delegation chain linking human_principal to agent_principal, and a scope covering the task ID and a repo:<path> token for the workspace — a violation blocks the run and emits policy.decided with decision=deny.

Sandbox modes: workspace-write (runs in place), read-only (no new sandbox), isolated-worktree (an ephemeral git worktree, or a filtered local copy when the workspace isn't a Git repo — required for the claude-code/codex adapters). Budgets (max_duration_seconds, max_tool_calls, max_cost_usd) are enforced against measured, not estimated, cost — the claude-code adapter extracts real total_cost_usd from the CLI's own streamed output, and a budget configured without a measurable cost fails closed rather than passing silently. Every run writes events.jsonl, changes.json/changes.diff (redacted), and validator/adapter logs to its run directory as evidence.

This contract already has a real, verified execution behind it: the Phase 2 gate smoke (2026-06-11) ran a real task against GraphForge via Claude Code 2.1.162, measured cost US$ 0.3718, validators passing — evidence in .pose/specs/harness-fase2-gate-smoke/evidence/.

Real maturity state (verified, not aspirational)

  • Harness: go build/go test green across adapter, contextpkg, events, runner and sandbox packages.
  • Conductor: canonical packages (assembler, audit, gitprovider, graphbridge, harnessrun, policy, readmodel, resolver, secrets, workflows) tested green. A real concurrency bug was found and fixed during a sanitization cycle (internal/persistence, a double-unlock on an RWMutex) — recorded in docs/ROADMAP.md's "Estado verificado" section rather than silently patched.
  • Portal: 381 vitest tests passing on the canonical track (board, task detail, SSE timeline, approvals, Reliability/Cost/Policy dashboards). A later, dedicated UX audit (docs/PORTAL-V1-GAP-ANALYSIS.md, 2026-06-15) found real gaps outside that scope, still open: no onboarding/Home screen, POSE specs without an associated Conductor task never appear on the board, and a production auth bug (an empty build-time token makes the static Portal image send unauthenticated conductor-api requests, returning 401).
  • Deployment: docker-compose.prod.yaml wires all 21 services and is validated by compose config — it runs as a complete local stack. The real Kubernetes staging gate is blocked: hardening is done locally, but it has never been applied to an actual cluster (no kubectl/kind/ k3d/staging cluster available in this environment). There is no public endpoint today — app.harne8.com/api.harne8.com remain draft specs.

Known scope limits of this page

GraphForge's indexing pipeline internals and the Portal's full UI surface are still not covered in depth here — see GraphForge's own docs/roadmaps/GAP-ANALYSIS-*.md documents and docs/PORTAL-V1-GAP-ANALYSIS.md for that level of detail. Expand this page (or add sibling pages under docs-site/docs/) as those components stabilize public contracts worth documenting outside their own module AGENTS.md/ADRs.