Skip to content

Commit aaeaae9

Browse files
authored
feat: Prometheus /metrics endpoint and OTLP exporter (#1122) (#1135)
## Summary Adds Prometheus `/metrics` scrape endpoint and OTLP log exporter, closing the most significant telemetry gap (G1) for enterprise control-plane positioning. ### Phase 1: Prometheus /metrics endpoint - `GET /api/v1/metrics` -- unauthenticated Prometheus scrape target - `PrometheusCollector` with dedicated `CollectorRegistry` (no global pollution) - **5 metric families**: - `synthorg_agents_total{status, trust_level}` -- agent counts by status and trust level - `synthorg_tasks_total{status, agent}` -- task counts by lifecycle status and assigned agent - `synthorg_cost_total` -- total accumulated cost - `synthorg_budget_used_percent` / `synthorg_budget_monthly_usd` -- budget utilization - `synthorg_coordination_efficiency` / `synthorg_coordination_overhead_percent` -- push-updated by coordination collector - `synthorg_security_evaluations_total{verdict}` -- security verdict counter (bounded: allow/deny/escalate) - `set_prometheus_collector()` on `AppState` for deferred initialization - Returns 503 when collector not configured ### Phase 2: OTLP exporter - `SinkType.OTLP` + `OtlpProtocol` enum (HTTP/protobuf only; gRPC rejected at init with `NotImplementedError`) - `OtlpHandler` -- background daemon thread with batched export - Maps structlog events to OTLP log records via `self.format(record)` (runs ProcessorFormatter + foreign_pre_chain) with correlation IDs (request_id, task_id, agent_id) as trace context - Configurable: endpoint, protocol, headers, export interval, batch size, timeout - `SinkType.PROMETHEUS` returns `NullHandler` (pull-based, no log handler needed) ### Security hardening - CRLF validation on OTLP headers (header injection prevention) - SSRF: private/loopback IP rejection on OTLP endpoint - TLS warning for unencrypted HTTP with auth headers - `/metrics` always excluded from auth (even with custom `auth.exclude_paths`) - Security verdict label cardinality bounded to allow/deny/escalate ### Documentation - `docs/architecture/acg-glossary.md` -- bidirectional ACG-to-SynthOrg concept mapping - `docs/design/operations.md` -- /metrics endpoint (all 9 metric families with types/labels), OTLP sink, G1 status updated - `CLAUDE.md` -- metrics event constants, otlp_handler.py logging exception ## Test plan - 15,616 tests pass (13 new tests added) - Unit tests for: MetricsController (200/503 with body assertion), PrometheusCollector (init, refresh, partial failure, all metric families, gauge clearing, coordination push, security verdicts with validation), OtlpHandler (emit, batch-ready, drain, format via self.format(), gRPC rejection, export failure with mocked urllib, close-always-drains), build_handler dispatch (PROMETHEUS NullHandler, OTLP delegation), SinkConfig validation (Prometheus/OTLP fields, cross-type rejection, batch_size/timeout fields, CRLF headers, SSRF) ## Review coverage Pre-reviewed by 15 agents, 18 findings addressed. Post-PR reviewed by 14 local agents + 3 external reviewers (CodeRabbit, Copilot, Gemini), 21 additional findings addressed: - 4 CRITICAL: gRPC rejection, formatter bypass, stale gauges, auth exclusion - 8 MAJOR: unused OTEL deps, verdict cardinality, test determinism, CRLF/SSRF/TLS, setter logs, docs G1 status, budget metric text - 5 MEDIUM: 503 body assertion, /metrics docs, OTLP constants, task-by-agent gauge - 2 MINOR: docstring push/pull model, lock semantics - 1 INFRA: pre-push hook worker crashes (-n 4 for full-suite runs) **Deferred to #1148:** daily budget %, per-agent budget, per-agent task count infrastructure (requires CostTracker time-windowed and per-agent queries). Closes #1122 Closes #1124
1 parent a8c1194 commit aaeaae9

23 files changed

Lines changed: 1985 additions & 26 deletions

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ See `web/CLAUDE.md` for the full component inventory, design token rules, and po
8888
## Logging
8989

9090
- **Every module** with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`
91-
- **Never** use `import logging` / `logging.getLogger()` / `print()` in application code (exception: `observability/setup.py`, `observability/sinks.py`, `observability/syslog_handler.py`, and `observability/http_handler.py` may use stdlib `logging` and `print(..., file=sys.stderr)` for handler construction, bootstrap, and error reporting code that runs before or during logging system configuration)
91+
- **Never** use `import logging` / `logging.getLogger()` / `print()` in application code (exception: `observability/setup.py`, `observability/sinks.py`, `observability/syslog_handler.py`, `observability/http_handler.py`, and `observability/otlp_handler.py` may use stdlib `logging` and `print(..., file=sys.stderr)` for handler construction, bootstrap, and error reporting code that runs before or during logging system configuration)
9292
- **Variable name**: always `logger` (not `_logger`, not `log`)
93-
- **Event names**: always use constants from the domain-specific module under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED`, `API_AUDIT_QUERIED`, `API_AGENT_HEALTH_QUERIED`, `API_AGENT_HEALTH_TREND_MISSING`, `API_SECURITY_CONFIG_EXPORTED`, `API_SECURITY_CONFIG_IMPORTED`, `API_SECURITY_CONFIG_IMPORT_FAILED`, `API_COORDINATION_METRICS_QUERIED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`, `GIT_COMMAND_START` from `events.git`, `CONTEXT_BUDGET_FILL_UPDATED`, `CONTEXT_BUDGET_COMPACTION_STARTED`, `CONTEXT_BUDGET_COMPACTION_COMPLETED`, `CONTEXT_BUDGET_COMPACTION_FAILED`, `CONTEXT_BUDGET_COMPACTION_SKIPPED`, `CONTEXT_BUDGET_COMPACTION_FALLBACK`, `CONTEXT_BUDGET_INDICATOR_INJECTED`, `CONTEXT_BUDGET_AGENT_COMPACTION_REQUESTED`, `CONTEXT_BUDGET_EPISTEMIC_MARKERS_PRESERVED` from `events.context_budget`, `BACKUP_STARTED` from `events.backup`, `SETUP_COMPLETED` from `events.setup`, `ROUTING_CANDIDATE_SELECTED` from `events.routing`, `SHIPPING_HTTP_BATCH_SENT` from `events.shipping`, `EVAL_REPORT_COMPUTED` from `events.evaluation`, `PROMPT_PROFILE_SELECTED` from `events.prompt`, `PROCEDURAL_MEMORY_START` from `events.procedural_memory`, `PERF_LLM_JUDGE_STARTED` from `events.performance`, `TASK_ENGINE_OBSERVER_FAILED` from `events.task_engine`, `WORKFLOW_EXEC_COMPLETED` from `events.workflow_execution`, `BLUEPRINT_INSTANTIATE_START` from `events.blueprint`, `WORKFLOW_DEF_ROLLED_BACK` from `events.workflow_definition`, `WORKFLOW_VERSION_SAVED` from `events.workflow_version`, `MEMORY_FINE_TUNE_STARTED`, `MEMORY_SELF_EDIT_TOOL_EXECUTE`, `MEMORY_SELF_EDIT_CORE_READ`, `MEMORY_SELF_EDIT_CORE_WRITE`, `MEMORY_SELF_EDIT_CORE_WRITE_REJECTED`, `MEMORY_SELF_EDIT_ARCHIVAL_SEARCH`, `MEMORY_SELF_EDIT_ARCHIVAL_WRITE`, `MEMORY_SELF_EDIT_RECALL_READ`, `MEMORY_SELF_EDIT_RECALL_WRITE`, `MEMORY_SELF_EDIT_WRITE_FAILED` from `events.memory`, `REPORTING_GENERATION_STARTED` from `events.reporting`, `RISK_BUDGET_SCORE_COMPUTED` from `events.risk_budget`, `LLM_STRATEGY_SYNTHESIZED` and `DISTILLATION_CAPTURED` from `events.consolidation`, `MEMORY_DIVERSITY_RERANKED`, `MEMORY_DIVERSITY_RERANK_FAILED`, and `MEMORY_REFORMULATION_ROUND` from `events.memory`, `NOTIFICATION_DISPATCHED` and `NOTIFICATION_DISPATCH_FAILED` from `events.notification`, `QUALITY_STEP_CLASSIFIED` from `events.quality`, `HEALTH_TICKET_EMITTED` from `events.health`, `TRAJECTORY_SCORING_START` from `events.trajectory`, `COORD_METRICS_AMDAHL_COMPUTED` and `COORD_METRICS_COLLECTION_COMPLETED` from `events.coordination_metrics`, `COORDINATION_STARTED`, `COORDINATION_COMPLETED`, `COORDINATION_FAILED`, `COORDINATION_PHASE_STARTED`, `COORDINATION_PHASE_COMPLETED`, `COORDINATION_PHASE_FAILED`, `COORDINATION_WAVE_STARTED`, `COORDINATION_WAVE_COMPLETED`, `COORDINATION_TOPOLOGY_RESOLVED`, `COORDINATION_CLEANUP_STARTED`, `COORDINATION_CLEANUP_COMPLETED`, `COORDINATION_CLEANUP_FAILED`, `COORDINATION_WAVE_BUILT`, `COORDINATION_FACTORY_BUILT`, and `COORDINATION_ATTRIBUTION_BUILT` from `events.coordination`, `WEB_REQUEST_START` and `WEB_SSRF_BLOCKED` from `events.web`, `DB_QUERY_START` and `DB_WRITE_BLOCKED` from `events.database`, `TERMINAL_COMMAND_START` and `TERMINAL_COMMAND_BLOCKED` from `events.terminal`, `SUB_CONSTRAINT_RESOLVED` and `SUB_CONSTRAINT_DENIED` from `events.sub_constraint`, `VERSION_SAVED` and `VERSION_SNAPSHOT_FAILED` from `events.versioning`, `ANALYTICS_AGGREGATION_COMPUTED` and `ANALYTICS_RETRY_RATE_ALERT` from `events.analytics`, `CALL_CLASSIFICATION_COMPUTED` from `events.call_classification`, `QUOTA_THRESHOLD_ALERT` and `QUOTA_POLL_FAILED` from `events.quota`, `CONFLICT_DEBATE_EVALUATOR_FAILED` from `events.conflict`, `DELEGATION_LOOP_CIRCUIT_BACKOFF` and `DELEGATION_LOOP_CIRCUIT_PERSIST_FAILED` from `events.delegation`, `MEETING_EVENT_COOLDOWN_SKIPPED` and `MEETING_TASKS_CAPPED` from `events.meeting`, `PERSISTENCE_CIRCUIT_BREAKER_SAVED`, `PERSISTENCE_CIRCUIT_BREAKER_SAVE_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_LOADED`, `PERSISTENCE_CIRCUIT_BREAKER_LOAD_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_DELETED`, and `PERSISTENCE_CIRCUIT_BREAKER_DELETE_FAILED` from `events.persistence`). Each domain has its own module -- see `src/synthorg/observability/events/` for the full inventory of constants. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
93+
- **Event names**: always use constants from the domain-specific module under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`, `GIT_COMMAND_START` from `events.git`, `CONTEXT_BUDGET_FILL_UPDATED`, `CONTEXT_BUDGET_COMPACTION_STARTED`, `CONTEXT_BUDGET_COMPACTION_COMPLETED`, `CONTEXT_BUDGET_COMPACTION_FAILED`, `CONTEXT_BUDGET_COMPACTION_SKIPPED`, `CONTEXT_BUDGET_COMPACTION_FALLBACK`, `CONTEXT_BUDGET_INDICATOR_INJECTED`, `CONTEXT_BUDGET_AGENT_COMPACTION_REQUESTED`, `CONTEXT_BUDGET_EPISTEMIC_MARKERS_PRESERVED` from `events.context_budget`, `BACKUP_STARTED` from `events.backup`, `SETUP_COMPLETED` from `events.setup`, `ROUTING_CANDIDATE_SELECTED` from `events.routing`, `SHIPPING_HTTP_BATCH_SENT` from `events.shipping`, `EVAL_REPORT_COMPUTED` from `events.evaluation`, `PROMPT_PROFILE_SELECTED` from `events.prompt`, `PROCEDURAL_MEMORY_START` from `events.procedural_memory`, `PERF_LLM_JUDGE_STARTED` from `events.performance`, `TASK_ENGINE_OBSERVER_FAILED` from `events.task_engine`, `WORKFLOW_EXEC_COMPLETED` from `events.workflow_execution`, `BLUEPRINT_INSTANTIATE_START` from `events.blueprint`, `WORKFLOW_DEF_ROLLED_BACK` from `events.workflow_definition`, `WORKFLOW_VERSION_SAVED` from `events.workflow_version`, `MEMORY_FINE_TUNE_STARTED`, `MEMORY_SELF_EDIT_TOOL_EXECUTE`, `MEMORY_SELF_EDIT_CORE_READ`, `MEMORY_SELF_EDIT_CORE_WRITE`, `MEMORY_SELF_EDIT_CORE_WRITE_REJECTED`, `MEMORY_SELF_EDIT_ARCHIVAL_SEARCH`, `MEMORY_SELF_EDIT_ARCHIVAL_WRITE`, `MEMORY_SELF_EDIT_RECALL_READ`, `MEMORY_SELF_EDIT_RECALL_WRITE`, `MEMORY_SELF_EDIT_WRITE_FAILED` from `events.memory`, `REPORTING_GENERATION_STARTED` from `events.reporting`, `RISK_BUDGET_SCORE_COMPUTED` from `events.risk_budget`, `LLM_STRATEGY_SYNTHESIZED` and `DISTILLATION_CAPTURED` from `events.consolidation`, `MEMORY_DIVERSITY_RERANKED`, `MEMORY_DIVERSITY_RERANK_FAILED`, and `MEMORY_REFORMULATION_ROUND` from `events.memory`, `NOTIFICATION_DISPATCHED` and `NOTIFICATION_DISPATCH_FAILED` from `events.notification`, `QUALITY_STEP_CLASSIFIED` from `events.quality`, `HEALTH_TICKET_EMITTED` from `events.health`, `TRAJECTORY_SCORING_START` from `events.trajectory`, `COORD_METRICS_AMDAHL_COMPUTED` from `events.coordination_metrics`, `COORDINATION_STARTED`, `COORDINATION_COMPLETED`, `COORDINATION_FAILED`, `COORDINATION_PHASE_STARTED`, `COORDINATION_PHASE_COMPLETED`, `COORDINATION_PHASE_FAILED`, `COORDINATION_WAVE_STARTED`, `COORDINATION_WAVE_COMPLETED`, `COORDINATION_TOPOLOGY_RESOLVED`, `COORDINATION_CLEANUP_STARTED`, `COORDINATION_CLEANUP_COMPLETED`, `COORDINATION_CLEANUP_FAILED`, `COORDINATION_WAVE_BUILT`, `COORDINATION_FACTORY_BUILT`, and `COORDINATION_ATTRIBUTION_BUILT` from `events.coordination`, `WEB_REQUEST_START` and `WEB_SSRF_BLOCKED` from `events.web`, `DB_QUERY_START` and `DB_WRITE_BLOCKED` from `events.database`, `TERMINAL_COMMAND_START` and `TERMINAL_COMMAND_BLOCKED` from `events.terminal`, `SUB_CONSTRAINT_RESOLVED` and `SUB_CONSTRAINT_DENIED` from `events.sub_constraint`, `VERSION_SAVED` and `VERSION_SNAPSHOT_FAILED` from `events.versioning`, `ANALYTICS_AGGREGATION_COMPUTED` and `ANALYTICS_RETRY_RATE_ALERT` from `events.analytics`, `CALL_CLASSIFICATION_COMPUTED` from `events.call_classification`, `QUOTA_THRESHOLD_ALERT` and `QUOTA_POLL_FAILED` from `events.quota`, `CONFLICT_DEBATE_EVALUATOR_FAILED` from `events.conflict`, `DELEGATION_LOOP_CIRCUIT_BACKOFF` and `DELEGATION_LOOP_CIRCUIT_PERSIST_FAILED` from `events.delegation`, `MEETING_EVENT_COOLDOWN_SKIPPED` and `MEETING_TASKS_CAPPED` from `events.meeting`, `PERSISTENCE_CIRCUIT_BREAKER_SAVED`, `PERSISTENCE_CIRCUIT_BREAKER_SAVE_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_LOADED`, `PERSISTENCE_CIRCUIT_BREAKER_LOAD_FAILED`, `PERSISTENCE_CIRCUIT_BREAKER_DELETED`, and `PERSISTENCE_CIRCUIT_BREAKER_DELETE_FAILED` from `events.persistence`, `METRICS_SCRAPE_COMPLETED`, `METRICS_SCRAPE_FAILED`, `METRICS_COLLECTOR_INITIALIZED`, `METRICS_COORDINATION_RECORDED`, `METRICS_OTLP_EXPORT_COMPLETED` and `METRICS_OTLP_FLUSHER_STOPPED` from `events.metrics`). Each domain has its own module -- see `src/synthorg/observability/events/` for the full inventory of constants. Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
9494
- **Structured kwargs**: always `logger.info(EVENT, key=value)` -- never `logger.info("msg %s", val)`
9595
- **All error paths** must log at WARNING or ERROR with context before raising
9696
- **All state transitions** must log at INFO

docs/DESIGN_SPEC.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ The design specification has been split into focused documentation pages for bet
3232
| [Decision Log](architecture/decisions.md) | All design decisions, organized by domain |
3333
| [Research & Prior Art](reference/research.md) | Framework comparison and scaling research |
3434
| [Industry Standards](reference/standards.md) | MCP, A2A, and other standards |
35+
| [ACG Glossary](architecture/acg-glossary.md) | Bidirectional ACG-to-SynthOrg concept mapping |
3536
| [Open Questions & Risks](roadmap/open-questions.md) | Unresolved questions and risk mitigations |
3637
| [Future Vision](roadmap/future-vision.md) | Backlog features and scaling path |

docs/architecture/acg-glossary.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
---
2+
description: Bidirectional mapping between ACG formalism concepts and SynthOrg equivalents.
3+
---
4+
5+
# ACG Glossary
6+
7+
Bidirectional mapping between the Agentic Computation Graph (ACG) formalism
8+
([arXiv:2603.22386](https://arxiv.org/abs/2603.22386)) and SynthOrg's architecture.
9+
10+
For the full evaluation including survey findings validation, structural credit assignment
11+
design, and agent pruning recommendations, see
12+
[`docs/research/acg-formalism-evaluation.md`](../research/acg-formalism-evaluation.md).
13+
14+
---
15+
16+
## ACG to SynthOrg
17+
18+
### Core Graph Concepts
19+
20+
| ACG Concept | SynthOrg Equivalent | Source | Fidelity | Notes |
21+
|---|---|---|---|---|
22+
| ACG Template | `CompanyConfig` + Company YAML / `WorkflowDefinition` | `core/company.py`, `config/schema.py` | Partial | ACG templates are graph-level (workflow topology). SynthOrg's YAML is org-level (agent roster, tool permissions, budget). `WorkflowDefinition` is the closer analogue for workflow templates. |
23+
| Realized Graph | `AgentContext` + `TaskExecution` + `CoordinationResult` | `engine/context.py`, `engine/coordination/models.py` | Strong | The realized graph IS the running state -- context, history, accumulated cost, current position. Multi-agent coordination adds `CoordinationPhaseResult` per phase. |
24+
| Execution Trace | `tuple[TurnRecord, ...]` in `ExecutionResult` + observability events | `engine/loop_protocol.py`, `observability/events/` | Strong | SynthOrg's trace is richer than ACG baseline: per-turn cost, token usage, tool fingerprints, stagnation signals, quality scores. numerous event constant domains (see `observability/events/`). |
25+
| Nodes (atomic actions) | LLM calls (`call_provider`), tool invocations (`execute_tool_calls`), validation gates (`check_budget`, `check_stagnation`) | `engine/loop_helpers.py` | Partial | Node typing is implicit in loop control flow, not a first-class abstraction. There is no `Node` type -- actions are identified by function names and turn records. |
26+
| Edges (control/data flow) | `SubtaskDefinition.dependencies` DAG, `DecompositionPlan.dependency_edges` | `engine/decomposition/models.py` | Strong (multi-agent) | Edges are explicit in multi-agent decomposition (dependency DAG). Implicit in single-agent loops (sequential execution order, no formal edge representation). |
27+
| Scheduling Policies | `AutoLoopConfig` + `select_loop_type()` + `CoordinationConfig` + `AutoTopologyConfig` | `engine/loop_selector.py`, `engine/routing/models.py` | Strong | Three-way loop selection (react/plan-execute/hybrid) and topology selection (SAS/centralized/decentralized/context-dependent) are scheduling policies. Budget-aware downgrade is a resource-constrained policy. |
28+
29+
### Dynamic Behavior Concepts
30+
31+
| ACG Concept | SynthOrg Equivalent | Source | Fidelity | Notes |
32+
|---|---|---|---|---|
33+
| Conditional branching | HybridLoop replan decisions, PlanExecuteLoop step completion | `engine/hybrid_loop.py`, `engine/hybrid_helpers.py` | Partial | Branching is embedded in loop logic (replan if step fails), not graph-level conditional edges. No formal "if node X succeeds, take edge Y" representation. |
34+
| Parallel composition | `ParallelExecutor`, `CoordinationWave`, `asyncio.TaskGroup` | `engine/parallel.py`, `engine/coordination/models.py` | Strong | Parallel waves in coordination are first-class. `ParallelExecutor` handles concurrent subtask dispatch with `fail_fast` semantics. |
35+
| Graph mutation | Hybrid replanning (`attempt_replan`), stagnation correction injection | `engine/hybrid_helpers.py`, `engine/stagnation/` | Partial | Replanning mutates the execution plan (new subtask list). Stagnation correction injects a new message. These are graph mutations but are not described in those terms. |
36+
| Termination conditions | `TerminationReason` enum (7 values: COMPLETED, MAX_TURNS, BUDGET_EXHAUSTED, SHUTDOWN, PARKED, STAGNATION, ERROR) | `engine/loop_protocol.py` | Strong | Richer than typical ACG termination models. 7 named reasons provide precise signal for recovery and routing decisions. |
37+
38+
### Resource and Cost Concepts
39+
40+
| ACG Concept | SynthOrg Equivalent | Source | Fidelity | Notes |
41+
|---|---|---|---|---|
42+
| Node cost | `TurnRecord.cost_usd` per turn, `TokenUsage` per completion | `engine/loop_protocol.py`, `providers/models.py` | Strong | Per-turn cost tracking with provider breakdown. Accumulated over execution via `ctx.accumulated_cost`. |
43+
| Resource constraints | `BudgetEnforcer` (3-layer), quota degradation, context budget | `budget/enforcer.py`, `engine/context_budget.py` | Strong | SynthOrg's resource model is more sophisticated than ACG: multi-layer enforcement, per-agent daily limits, context fill tracking, risk budget. |
44+
| Quality-cost tradeoffs | Budget-aware loop downgrade (hybrid->plan_execute at 80%), model auto-downgrade, quota degradation strategies | `engine/loop_selector.py`, `budget/enforcer.py` | Strong | Explicit tradeoff mechanisms with hard budget caps. Downgrade only at task boundaries (consistency guarantee). |
45+
46+
---
47+
48+
## SynthOrg to ACG
49+
50+
Reverse lookup for readers starting from SynthOrg terminology.
51+
52+
| SynthOrg Concept | ACG Equivalent | Notes |
53+
|---|---|---|
54+
| `CompanyConfig` / Company YAML | ACG Template | Org-level; `WorkflowDefinition` maps more precisely to graph-level templates |
55+
| `AgentContext` + `TaskExecution` | Realized Graph | Running state with full context |
56+
| `TurnRecord` tuple | Execution Trace | Per-turn cost/token data exceeds ACG baseline |
57+
| LLM calls, tool invocations, validation gates | Nodes | Implicit typing via function names, not a `Node` type |
58+
| `SubtaskDefinition.dependencies` | Edges | Explicit in multi-agent DAG, implicit in single-agent |
59+
| `AutoLoopConfig` + `select_loop_type()` | Scheduling Policies | 3-way loop + topology selection |
60+
| `HybridLoop` replan | Conditional branching + Graph mutation | Embedded in loop logic |
61+
| `ParallelExecutor`, `CoordinationWave` | Parallel composition | First-class with `fail_fast` |
62+
| `TerminationReason` (7 values) | Termination conditions | Richer taxonomy |
63+
| `BudgetEnforcer` (3-layer) | Resource constraints | Multi-layer enforcement exceeds ACG |
64+
| `TurnRecord.cost_usd`, `TokenUsage` | Node cost | Per-turn + per-completion |
65+
| Budget-aware downgrade | Quality-cost tradeoffs | Task-boundary-only downgrade |
66+
67+
---
68+
69+
## SynthOrg Extensions Beyond ACG
70+
71+
The following SynthOrg concepts have no equivalent in the ACG formalism:
72+
73+
| Concept | Module | Description |
74+
|---|---|---|
75+
| **Progressive trust** | `security/trust/service.py` | Agent trust levels (RESTRICTED/STANDARD/ELEVATED) with mandatory human approval for promotion. |
76+
| **Personality and behavioral config** | `core/personality.py` | Big Five traits + behavioral enums affecting decision style. |
77+
| **Memory injection** | `memory/retrieval/` | Episodic and procedural memory retrieval shaping context before execution. |
78+
| **Prompt profiles** | `engine/prompt/profiles.py` | Verbosity adaptation by model tier. |
79+
| **Autonomy levels** | `security/autonomy/resolver.py` | 4 presets (full/semi/supervised/locked) with tool permission gating. |
80+
81+
---
82+
83+
## ACG Concepts SynthOrg Handles Differently
84+
85+
Where fidelity is "Partial," SynthOrg implements the concept but through different
86+
abstractions than ACG prescribes:
87+
88+
- **Node typing**: ACG defines explicit node types. SynthOrg's nodes are implicit in loop
89+
control flow -- actions are identified by function names and turn records, not a `Node`
90+
type. A lightweight `NodeType` enum (LLM_CALL, TOOL_INVOCATION, QUALITY_CHECK, etc.)
91+
on `TurnRecord` is a recommended future addition.
92+
93+
- **Conditional branching**: ACG uses graph-level conditional edges. SynthOrg embeds
94+
branching in loop logic (replan if step fails), without formal "if node X succeeds,
95+
take edge Y" representation.
96+
97+
- **Graph mutation**: ACG describes runtime graph topology changes. SynthOrg's replanning
98+
and stagnation correction are functionally equivalent but are not described in graph
99+
mutation terms internally.

0 commit comments

Comments
 (0)