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
8 changes: 5 additions & 3 deletions a0/router.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 57:5 0:0 4:8
# 59:5 0:0 4:8
from __future__ import annotations

import os
from pathlib import Path
from .contract import A0Request, A0Response, normalize_hmmm
from .logging import log_event
Expand All @@ -11,7 +12,8 @@
from .tools.pdf_tool import run_pdf_extract
from .tools.whisper_tool import run_whisper_segments

LOG_DIR = Path(__file__).resolve().parent / "logs"
_DEFAULT_LOG_DIR = Path(__file__).resolve().parent / "logs"
LOG_DIR = Path(os.environ.get("A0_LOG_DIR", _DEFAULT_LOG_DIR))


Comment on lines +16 to 18
def _select_adapter(req: A0Request):
Expand Down Expand Up @@ -76,4 +78,4 @@ def handle(req: A0Request) -> A0Response:
"hmmm": hmmm,
})
return A0Response(task_id=req.task_id, result={"text": resp.get("text", ""), "artifacts": []}, hmmm=hmmm)
# 57:5 0:0 4:8
# 59:5 0:0 4:8
8 changes: 5 additions & 3 deletions a0/state.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# 12:0 0:0 2:0
# 14:0 0:0 2:0
from __future__ import annotations

import json
import os
from pathlib import Path
from typing import Any, Dict

STATE_PATH = Path(__file__).resolve().parent / "state" / "a0_state.json"
_DEFAULT_STATE_PATH = Path(__file__).resolve().parent / "state" / "a0_state.json"
STATE_PATH = Path(os.environ.get("A0_STATE_PATH", _DEFAULT_STATE_PATH))

def load_state() -> Dict[str, Any]:
if STATE_PATH.exists():
Expand All @@ -15,4 +17,4 @@ def load_state() -> Dict[str, Any]:
def save_state(state: Dict[str, Any]) -> None:
STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
STATE_PATH.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8")
# 12:0 0:0 2:0
# 14:0 0:0 2:0
33 changes: 26 additions & 7 deletions python/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 163:21 0:0 0:31
# 162:41 0:0 0:31
from .chat import router as chat_router
from .agents import router as agents_router
from .memory import router as memory_router
Expand Down Expand Up @@ -87,11 +87,11 @@ def collect_ui_meta() -> list[dict]:
"python.routes.editable_schema",
"python.routes.cli",
"python.routes.liminals",
"python.routes.artifacts",
# transcripts is a standalone page (routed at /transcripts via the top
# nav), not a metadata-driven console tab. Its UI_META is a page-nav
# descriptor (path, no tab_id/sections) and must not be aggregated here,
# or the console renders an unrenderable placeholder tab for it.
# artifacts (the "Archive") and transcripts are standalone pages,
# routed at /archive and /transcripts via the top nav, not
# metadata-driven console tabs. Their UI_META has no custom renderer
# and no sections, so aggregating them here makes the console render an
# unrenderable placeholder tab (caught by the console-tab guard).
"python.routes.fleet",
]
tabs = []
Expand Down Expand Up @@ -195,7 +195,26 @@ def collect_doc_meta() -> list[dict]:
# Stale ALLOWLIST entries (route no longer exists) also fail.
# class: security
# call: python.tests.contracts.route_gating.test_every_write_route_is_gated
#
# id: routes_doc_blocks_complete
# given: every python/routes/*.py file (excluding __init__.py)
# then: it declares # DOC module/label/description/tier/role exactly
# once each, with role drawn from the allowed doctrine set
# class: correctness
# call: python.tests.contracts.module_doctrine.test_route_doc_blocks_are_complete
#
# id: routes_files_annotated
# given: every python/routes/*.py file (excluding __init__.py)
# then: its first and last non-blank lines are # N:M annotation comments
# class: correctness
# call: python.tests.contracts.module_doctrine.test_route_files_are_annotated
#
# id: routes_routers_registered
# given: every python/routes/*.py file that defines a module-level router
# then: it is imported and added to ALL_ROUTERS in __init__.py
# class: correctness
# call: python.tests.contracts.module_doctrine.test_router_defining_files_are_registered
# === END CONTRACTS ===
# 171:16

# 163:21 0:0 0:31
# 162:41 0:0 0:31
6 changes: 4 additions & 2 deletions python/routes/_admin_gate.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# 29:21 0:0 11:2
# 29:23 0:0 11:2
# DOC module: _admin_gate
# DOC label: Admin Gate
# DOC description: Shared write-gate for instrument-wide mutation endpoints.
# DOC tier: admin
# DOC role: service
"""Shared admin / operator gate for routes that mutate global instrument
state (memory seeds, PCNA channels, sigma watches, system toggles, agents, deals).

Expand Down Expand Up @@ -55,4 +57,4 @@ async def require_admin(request: Request) -> None:
except Exception:
pass
raise HTTPException(status_code=403, detail="Admin only")
# 29:21 0:0 11:2
# 29:23 0:0 11:2
5 changes: 3 additions & 2 deletions python/routes/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# 77:7 0:0 1:1
# 77:8 0:0 1:1
# DOC module: admin
# DOC label: Admin Email Allowlist
# DOC description: Admin-only endpoints for listing, adding, and removing admin email allowlist entries.
# DOC tier: admin
# DOC role: route
# DOC endpoint: GET /api/v1/admin/emails | List configured admin email entries.
# DOC endpoint: POST /api/v1/admin/emails | Add an admin email entry.
# DOC endpoint: DELETE /api/v1/admin/emails/{email} | Remove an admin email entry.
Expand Down Expand Up @@ -100,4 +101,4 @@ async def remove_admin_email(request: Request, email: str):
if result.rowcount == 0:
return JSONResponse(status_code=404, content={"error": "Not found"})
return {"ok": True, "email": target}
# 77:7 0:0 1:1
# 77:8 0:0 1:1
5 changes: 3 additions & 2 deletions python/routes/agents.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 265:31 1:3 2:8
# 265:32 1:3 2:8
import time
import logging
from fastapi import APIRouter, HTTPException, Request
Expand Down Expand Up @@ -30,6 +30,7 @@
# DOC label: Agents
# DOC description: Manages agent instances and sub-agent spawning. Lists running agents and supports manual merge operations.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/agents | List all agent instances
# DOC endpoint: POST /api/v1/agents/spawn | Spawn a new agent instance
# DOC endpoint: POST /api/v1/agents/{name}/merge | Merge a named agent configuration
Expand Down Expand Up @@ -324,4 +325,4 @@ def _num(v, caster):
}


# 265:31 1:3 2:8
# 265:32 1:3 2:8
5 changes: 3 additions & 2 deletions python/routes/approval_scopes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 118:18 3:4 1:4
# 118:19 3:4 1:4
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel

Expand All @@ -9,6 +9,7 @@
# DOC label: Approval Scopes
# DOC description: Pre-approved action scope registry. Users can grant the agent permission to perform specific write actions without a per-action approval prompt. Viewing the catalog is free; granting and revoking requires ws tier.
# DOC tier: ws
# DOC role: route
# DOC endpoint: GET /api/v1/approval-scopes/catalog | List all available scope categories and their actions
# DOC endpoint: GET /api/v1/approval-scopes | List the current user's granted scopes
# DOC endpoint: POST /api/v1/approval-scopes | Grant a scope to the current user
Expand Down Expand Up @@ -160,4 +161,4 @@ async def revoke_approval_scope(scope: str, request: Request):
patch_endpoint="/api/v1/approval-scopes",
query_key="/api/v1/approval-scopes",
))
# 118:18 3:4 1:4
# 118:19 3:4 1:4
5 changes: 3 additions & 2 deletions python/routes/artifacts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 104:16 2:5 3:1
# 104:17 2:5 3:1
"""HTTP API for the unified artifacts archive."""
from typing import Optional
import datetime as _dt
Expand All @@ -13,6 +13,7 @@
# DOC label: Archive
# DOC description: Unified archive of every file a0 produces (images, reports, evidence). Backed by Replit Object Storage with sha256 dedupe and full provenance.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/artifacts | List artifacts with filters (kind, tool, since)
# DOC endpoint: GET /api/v1/artifacts/{id} | Fetch a single artifact + provenance
# DOC endpoint: GET /api/v1/artifacts/{id}/download | Stream the artifact bytes
Expand Down Expand Up @@ -146,4 +147,4 @@ async def patch_artifact(artifact_id: str, body: PatchArtifact, request: Request
if not row:
raise HTTPException(status_code=404, detail="artifact not found")
return _serialize(row)
# 104:16 2:5 3:1
# 104:17 2:5 3:1
5 changes: 3 additions & 2 deletions python/routes/billing.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 418:311 5:7 2:4
# 418:312 5:7 2:4
import os
import stripe
from urllib.parse import urlparse
Expand All @@ -14,6 +14,7 @@
# DOC label: Billing
# DOC description: Donations-only billing surface. a0p is a research instrument, not a subscription product — there is no recurring sign-up tier. Existing Supporter subscribers are honored until they cancel via the Stripe portal. ws tier auto-assigned to @interdependentway.org accounts; admin tier reserved for the owner + invited collaborators.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/billing/status | Get current user billing status and tier
# DOC endpoint: GET /api/v1/billing/plans | List supported flows (donation only; legacy Supporter tier retired)
# DOC endpoint: GET /api/v1/billing/funding-statement | Verbatim 501c3/$500 disclosure copy
Expand Down Expand Up @@ -858,4 +859,4 @@ async def explainer_checkout(request: Request):
# class: idempotency
# call: python.tests.contracts.billing.test_webhook_replay_is_idempotent
# === END CONTRACTS ===
# 418:311 5:7 2:4
# 418:312 5:7 2:4
5 changes: 3 additions & 2 deletions python/routes/billing_helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# 7:20 0:0 1:0
# 7:21 0:0 1:0
# DOC module: billing_helpers
# DOC label: Billing Helpers
# DOC description: Legacy Supporter-tier helper functions used by billing webhook handling paths.
# DOC tier: admin
# DOC role: service
"""
Billing helpers — Supporter-tier shims kept alive only for legacy webhook traffic.

Expand Down Expand Up @@ -32,4 +33,4 @@ def is_supporter_subscription(metadata: dict) -> bool:
legacy Supporter sub. Used to filter webhook events so we only
touch rows that were actually Supporter subscribers."""
return metadata.get("product_key") == "supporter"
# 7:20 0:0 1:0
# 7:21 0:0 1:0
5 changes: 3 additions & 2 deletions python/routes/chat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 637:186 2:7 2:16
# 637:187 2:7 2:16
import time
import traceback
from fastapi import APIRouter, HTTPException, Request
Expand Down Expand Up @@ -94,6 +94,7 @@ def _attach_cost_usd(usage: dict | None, provider_id: str | None) -> None:
# DOC label: Chat
# DOC description: Manages conversations and messages between users and the agent. Supports streaming replies, conversation history, and per-conversation metadata.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/conversations | List all conversations for the current user
# DOC endpoint: POST /api/v1/conversations | Create a new conversation
# DOC endpoint: GET /api/v1/conversations/{id} | Get a single conversation
Expand Down Expand Up @@ -905,4 +906,4 @@ async def _grant_scope_if_valid(scope: str) -> tuple[bool, str]:
# class: correctness
# call: python.tests.contracts.chat.test_unknown_body_model_400
# === END CONTRACTS ===
# 637:186 2:7 2:16
# 637:187 2:7 2:16
5 changes: 3 additions & 2 deletions python/routes/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# 124:88 2:4 1:6
# 124:89 2:4 1:6
# DOC module: cli
# DOC label: CLI Keys
# DOC description: API key management for CLI and Termux access. Users generate bearer tokens (a0k_...) used to authenticate one-shot or interactive terminal sessions without a browser session.
# DOC tier: ws
# DOC role: route
# DOC endpoint: POST /api/v1/cli/keys | Generate a new CLI API key
# DOC endpoint: GET /api/v1/cli/keys | List your CLI keys
# DOC endpoint: DELETE /api/v1/cli/keys/{key_id} | Revoke a CLI key
Expand Down Expand Up @@ -249,4 +250,4 @@ async def cli_chat(body: CliChatBody, request: Request):
"tier": tier,
"usage": usage,
}
# 124:88 2:4 1:6
# 124:89 2:4 1:6
5 changes: 3 additions & 2 deletions python/routes/contexts.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 80:168 0:2 2:2
# 80:169 0:2 2:2
import math
import os
from fastapi import APIRouter, HTTPException, Request
Expand All @@ -13,6 +13,7 @@
# DOC label: Contexts
# DOC description: Manages named prompt context blocks injected into the agent's system prompt. Admin-only. Each context is a named text value retrieved by the agent at inference time.
# DOC tier: admin
# DOC role: route
# DOC endpoint: GET /api/v1/contexts/{name} | Get a named prompt context value
# DOC endpoint: PUT /api/v1/contexts/{name} | Set or replace a named prompt context value

Expand Down Expand Up @@ -293,4 +294,4 @@ async def save_core_context(body: CoreContextBody, request: Request):
patch_endpoint="/api/v1/context/system-sections",
query_key="/api/v1/contexts",
))
# 80:168 0:2 2:2
# 80:169 0:2 2:2
5 changes: 3 additions & 2 deletions python/routes/docs.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# 27:8 1:2 1:0
# 27:9 1:2 1:0
# DOC module: docs
# DOC label: Docs
# DOC description: Living API reference. Each route module self-declares its documentation via # DOC comment blocks in its source file; this module aggregates and serves them. Minimum required fields per module: module, label, description, tier.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/docs | Return all module documentation entries sorted by label
# DOC endpoint: GET /api/v1/docs/readme | Return replit.md content and per-module code:comment stats

Expand Down Expand Up @@ -41,4 +42,4 @@ async def get_readme(request: Request):
content = "# a0p\n\nNo README found."
modules = collect_doc_meta()
return {"content": content, "modules": modules}
# 27:8 1:2 1:0
# 27:9 1:2 1:0
5 changes: 3 additions & 2 deletions python/routes/edcm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 89:8 0:4 4:3
# 89:9 0:4 4:3
from fastapi import APIRouter, Request
from pydantic import BaseModel
from typing import Optional, Any
Expand All @@ -11,6 +11,7 @@
# DOC label: EDCM
# DOC description: Emotional-Dimensional Calibration Module. Tracks affective metrics and stores periodic snapshots of the agent's internal state dimensions.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/edcm/metrics | Get current EDCM metric values
# DOC endpoint: POST /api/v1/edcm/metrics | Update EDCM metrics
# DOC endpoint: GET /api/v1/edcm/snapshots | List historical EDCM snapshots
Expand Down Expand Up @@ -113,4 +114,4 @@ async def add_snapshot(request: Request, body: EdcmSnapshotInput):
await require_admin(request)
row = await storage.add_edcm_snapshot(body.model_dump(exclude_none=True))
return {"edcmbone_version": EDCMBONE_VERSION, "item": row}
# 89:8 0:4 4:3
# 89:9 0:4 4:3
5 changes: 3 additions & 2 deletions python/routes/editable_schema.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 38:8 1:2 1:1
# 38:9 1:2 1:1
import os
from fastapi import APIRouter, HTTPException, Request

Expand All @@ -8,6 +8,7 @@
# DOC label: Editable Schema
# DOC description: Machine-readable index of all registered mutable backend fields. WSEM fetches this index on activation to know what is editable, what control type to render, and which endpoint to PATCH. Also serves the project README with live per-module code:comment stats.
# DOC tier: ws
# DOC role: route
# DOC endpoint: GET /api/v1/editable-schema/index | Return all registered editable fields in camelCase (ws/admin only)
# DOC endpoint: GET /api/v1/editable-schema/readme | Return replit.md content and per-module stats (all authenticated users)

Expand Down Expand Up @@ -53,4 +54,4 @@ async def get_editable_schema_readme(request: Request):
content = "# a0p\n\nNo README found."
modules = collect_doc_meta()
return {"content": content, "modules": modules}
# 38:8 1:2 1:1
# 38:9 1:2 1:1
5 changes: 3 additions & 2 deletions python/routes/fleet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 392:32 2:10 1:4
# 392:33 2:10 1:4
# N:M
"""Fleet benchmarking — head-to-head comparison of model/agent/orchestration tuples.

Expand Down Expand Up @@ -26,6 +26,7 @@
# DOC label: Fleet
# DOC description: Persistent benchmarks — fan one prompt across N (model, agent, orchestration) tuples and compare.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/fleet/benchmarks | List user's saved benchmarks.
# DOC endpoint: POST /api/v1/fleet/benchmarks | Create a benchmark.
# DOC endpoint: GET /api/v1/fleet/benchmarks/{id} | Benchmark + contestants + recent runs.
Expand Down Expand Up @@ -487,4 +488,4 @@ async def list_runs(bid: int, request: Request):
"WHERE benchmark_id = :bid ORDER BY started_at DESC LIMIT 50"
), {"bid": bid})).mappings().all()
return [dict(r) for r in rows]
# 392:32 2:10 1:4
# 392:33 2:10 1:4
5 changes: 3 additions & 2 deletions python/routes/focus.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# 321:52 0:7 1:9
# 321:53 0:7 1:9
# DOC module: focus
# DOC label: Focus
# DOC description: Model focus management. Provides context boost injection per conversation, focus regain directives, per-conversation tool selection, and system prompt preview.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/conversations/{id}/boost | Get the context boost for a conversation
# DOC endpoint: PUT /api/v1/conversations/{id}/boost | Set context boost text injected into the system prompt
# DOC endpoint: DELETE /api/v1/conversations/{id}/boost | Clear the context boost
Expand Down Expand Up @@ -448,4 +449,4 @@ async def get_prompt_sections(conv_id: int, request: Request):
sections["has_messages"] = has_messages
sections["conversation_id"] = conv_id
return sections
# 321:52 0:7 1:9
# 321:53 0:7 1:9
5 changes: 3 additions & 2 deletions python/routes/forge.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 213:37 5:8 1:7
# 213:38 5:8 1:7
"""The Forge — character-sheet style agent instantiation.

Self-updating tool/model docs DB:
Expand All @@ -23,6 +23,7 @@
# DOC label: Forge
# DOC description: Character-sheet style agent creation. Pick a template archetype, swap in a model, check tools, set personality. Self-updating tool/model registry feeds the form.
# DOC tier: free
# DOC role: route
# DOC endpoint: GET /api/v1/forge/templates | List built-in archetype templates
# DOC endpoint: GET /api/v1/forge/tools | Live tool catalog (auto-introspected from TOOL_SCHEMAS_CHAT)
# DOC endpoint: GET /api/v1/forge/models | Live model catalog (delegated to model_catalog.list_models_for_user)
Expand Down Expand Up @@ -293,4 +294,4 @@ async def duel_stub(request: Request) -> dict:
def _jsonb(value) -> str:
import json
return json.dumps(value) if value is not None else "null"
# 213:37 5:8 1:7
# 213:38 5:8 1:7
Loading
Loading