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:
2026-07-17-pose-como-produto-standalone-...— the freemium boundary this page's responsibility table implements.2026-07-14-orquestra-o-multiagente-pertence-ao-conductor.md— why multi-agent orchestration lives in Conductor, not POSE or Harness.2026-06-23-harness-interactive-budget-approval.md— Harness's approval/budget contract for interactive runs.
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 queueconductor-v1):LoadContextPackage→RegisterClaim(taskCLAIMED→EXECUTING) →CreateAgentRun→ExecuteHarness(a failed validator/budget/policy check is not an infra error — it becomes aFAILEDrun with evidence, never a silent retry) →PersistRunResults→FinalizeRun(SUCCEEDED/FAILED; success moves the task toWAITING_HUMAN, the mandatory human review gate for the next cut).TaskOrchestrationWorkflowowns task state end to end: claim by signal, up to N attempts (each producing a fresh, checksummed Context Package), success →WAITING_HUMANwith an approval SLA (decision always attributed to a principal viaApprovalDecided— it never auto-approves; an expired SLA escalates, an expired escalation window blocks the task), approved →CreatePullRequest(no creator configured or an external error moves toWAITING_TOOLrather 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 testgreen 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 anRWMutex) — recorded indocs/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 unauthenticatedconductor-apirequests, returning401). - Deployment:
docker-compose.prod.yamlwires all 21 services and is validated bycompose 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 (nokubectl/kind/k3d/staging cluster available in this environment). There is no public endpoint today —app.harne8.com/api.harne8.comremain 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.