Skip to content

Coordination API

Endpoints built to serve multi-agent coding systems (planners, workers, refiners) that need a single source of truth for repo context, code structure, and agent activity. Born out of real needs from gitoma and similar autonomous-coder projects.

All endpoints live on the same Go gateway as the telemetry API (127.0.0.1:9999 by default) and are equally available through the MCP server. Contracts below are normative: the shape is stable, fields may be added but not removed or renamed without a version bump.

Status legend

  • ready — shipped and covered by a smoke test
  • partial — shipped; a specific field is best-effort and documented below
  • stub — endpoint exists, returns {"status":"not_implemented","reason":"..."} with HTTP 501 so clients can probe capability without errors

PLANNER (phase 2, one-shot)

GET /repo/context?target=<abs> — ready

Structural snapshot of a repo. Cheap to compute; safe to call every planning round.

json
{
  "target": "/abs/repo",
  "languages": [
    {"name": "Python",     "files": 42, "bytes": 180321},
    {"name": "TypeScript", "files": 18, "bytes":  62110}
  ],
  "stack": ["python/poetry", "npm"],
  "recent_churn": {
    "since_days": 7,
    "insertions": 1243,
    "deletions":  312,
    "files_touched": 17
  },
  "hot_files":    [{"path": "src/db.py", "changes": 12}],
  "stable_files": [{"path": "LICENSE",   "last_touched": "2024-11-12"}]
}

Stack detection rules (first match wins, multiple stacks possible): pyproject.tomlpython/poetry or python/hatch; requirements*.txtpython/pip; go.modgo; package.jsonnpm; Cargo.tomlrust/cargo; Dockerfile → adds docker; docker-compose.yml → adds docker-compose; any .github/workflows/* → adds github-actions.

GET /repo/blame/:path?target=<abs> — ready

Per-line blame with revert detection.

json
[
  {
    "line":   42,
    "commit": "a1b2c3d",
    "author": "alice@example.com",
    "agent":  "gitoma",
    "run_id": "run-42",
    "when":   "2026-04-22T10:00:00Z",
    "reverted_by": "d4e5f6a"
  }
]
  • agent and run_id come from the observations table joined on commit_sha. Null when the commit was not recorded by any MCP agent.
  • reverted_by is populated when a later commit message matches ^Revert and its diff inverts the blamed commit. Best-effort — not a guarantee.

GET /repo/agent-log?since=24h&limit=100 — ready

Append-only event log of agent activity. Backed by the observations table; written via POST /observation.

json
[
  {
    "id":            187,
    "ts":            "2026-04-23T14:00:00Z",
    "run_id":        "gitoma-run-42",
    "agent":         "gitoma",
    "subtask_id":    "sub-3",
    "model":         "claude-opus-4.7",
    "branch":        "feat/refactor-db",
    "commit_sha":    "a1b2c3d",
    "outcome":       "success",
    "touched_files": ["src/db.py", "tests/test_db.py"],
    "failure_modes": []
  }
]

since accepts a duration (24h, 7d, 30m) or an ISO-8601 lower bound on ts. Limit capped at 1000.

GET /repo/test-map?target=<abs> — stub (501)

Would return a forward + inverse mapping between test files and the source they exercise. Deferred until a test-runner wrapper contract is agreed (per-language). Current response:

json
{"status": "not_implemented", "reason": "needs test-runner integration (pytest/jest/go test)"}

GET /repo/failing-tests?target=<abs> — stub (501)

Same reason. Needs a way to ingest the last known test state — planned to hang off a new test_runs table written by a future analyzers/pytest.sh or similar adapter.

WORKER (phase 3, per-subtask)

GET /symbol?path=<abs>&name=<symbol> — partial (Python v1)

Returns structured info about a symbol defined in the given file.

json
{
  "name":      "get_conn",
  "kind":      "function",
  "signature": "def get_conn(db: str = 'app.db') -> sqlite3.Connection",
  "lineno":    12,
  "callers":   [{"file": "src/db.py", "line": 48}],
  "callees":   [{"name": "sqlite3.connect"}],
  "test_coverage": []
}
  • v1 scope: Python only, via analyzers/python-symbol-index.py (stdlib ast). callers/callees are in-file only (cross-file symbol index deferred — needs a repo-wide AST cache with invalidation).
  • test_coverage is always [] until test integration ships.

GET /file/imports?path=<abs> — ready (Python)

json
[
  {"module": "sqlite3", "symbol_imported": null, "line": 1},
  {"module": "typing",  "symbol_imported": "Optional", "line": 2}
]

Non-Python files return an empty array with language: "other" in the response envelope.

GET /file/exports?path=<abs> — ready (Python)

Top-level public definitions (name does not start with _).

json
[
  {"name": "get_conn", "kind": "function", "lineno": 12, "public": true},
  {"name": "Schema",   "kind": "class",    "lineno": 34, "public": true}
]

GET /file/frozen-regions?path=<abs> — stub (501)

Needs a contract. Two options under consideration:

  1. Inline comment markers: # occam:frozen / # occam:end-frozen
  2. External .occam-frozen.yml with {file, start_line, end_line, reason} entries

Current response:

json
{"status": "not_implemented", "reason": "needs frozen-region contract design (inline markers vs .occam-frozen.yml)"}

GET /file/last-safe?path=<abs> — stub (501)

"Last commit at which this file's tests passed". Requires the test-runs table; 501 for now.

POST /claim — ready

Optimistic file lock for coordinated workers.

http
POST /claim
Content-Type: application/json

{
  "path":         "/abs/path/to/file.py",
  "agent":        "gitoma",
  "run_id":       "run-42",
  "ttl_seconds":  600
}

Success (claim granted):

json
{"lock_id": "c8a1e4f2-…", "expires_at": "2026-04-23T14:10:00Z"}

Conflict (409 — already claimed):

json
{"error": "already_claimed", "held_by": {"agent": "other", "run_id": "run-41", "expires_at": "..."}}

Claims expire lazily (on next POST /claim or GET /claim). ttl_seconds is clamped to [30, 3600].

DELETE /claim?lock_id=<id> — ready

Releases the claim. Idempotent: returns {"released": true} whether the lock existed or not (protects against double-release on retries).

GET /claim?path=<abs> (single) or GET /claim (all active) — ready

Introspection.

REFINER / CRITICS (phase 3.5)

GET /diff?target=<abs>&base=<rev>&branch=<rev> — partial

json
{
  "touched_files": ["src/db.py", "tests/test_db.py"],
  "ast_top_level_delta": {
    "added":    [{"file": "src/db.py", "name": "init_schema", "kind": "function"}],
    "removed":  [],
    "modified": [{"file": "src/db.py", "name": "get_conn",   "kind": "function"}]
  },
  "tests_delta": {"status": "not_implemented"}
}

ast_top_level_delta is Python-only v1. tests_delta stubbed until test integration.

GET /run/:id/tests/delta — stub (501)

GET /scorecard/:run_id — stub (501)

Both depend on test-runs; same reason as above.

CROSS-CUTTING

GET /agent/identity/:commit — ready

Lookup an agent event by commit SHA.

json
{
  "agent":      "gitoma",
  "run_id":     "run-42",
  "subtask_id": "sub-3",
  "model":      "claude-opus-4.7",
  "confidence": 0.78,
  "ts":         "2026-04-23T14:00:00Z"
}

404 when no observation with that SHA is recorded.

GET /file/fingerprint?path=<abs> — partial

json
{
  "path":            "src/db.py",
  "sha":             "git-index-sha",
  "content_hash":    "sha256:…",
  "ast_hash":        "sha256:…",
  "test_coverage_hash": null
}
  • sha is the blob SHA in the git index (git ls-files -s); null if untracked.
  • content_hash is sha256 of on-disk bytes.
  • ast_hash is sha256 of a whitespace-insensitive AST dump (Python only). Two files with identical structure but different indentation have the same ast_hash.
  • test_coverage_hash always null v1.

GET /repo/churn/:path?target=<abs>&since=30d — ready

json
{
  "modifications": 14,
  "reverts":        2,
  "contributors":  ["alice@x", "gitoma-bot@x"]
}

Revert heuristic: commit message matching ^Revert OR containing revert adjacent to the commit SHA being reverted.

GET /contract?path=<abs> — partial

json
{
  "public_api":     [{"name": "get_conn", "kind": "function"}],
  "test_coverage":  0.0,
  "coupling_score": 0
}
  • public_api is sourced from /file/exports (Python).
  • test_coverage and coupling_score always 0 v1 until test integration + cross-file symbol index exist.

POST /observation — ready

Append-only log entry. Agents call this at the end of a run / subtask.

http
POST /observation
Content-Type: application/json

{
  "run_id":        "gitoma-run-42",
  "agent":         "gitoma",
  "subtask_id":    "sub-3",
  "model":         "claude-opus-4.7",
  "branch":        "feat/refactor-db",
  "commit_sha":    "a1b2c3d",
  "outcome":       "success",
  "touched_files": ["src/db.py"],
  "failure_modes": [],
  "confidence":    0.78,
  "extra":         {"anything": "else"}
}

Response: {"id": 187, "ts": "2026-04-23T14:00:00Z"}. Only run_id, agent, and outcome are required.

MCP tool mapping

Each endpoint above is also exposed as an MCP tool so agents can call it without HTTP. See MCP guide for the full tool list. Naming convention: occam_<endpoint_noun> — for example /repo/context → tool occam_repo_context, POST /observationoccam_observation.

Stability & versioning

The response version field in /analyze tracks the engine; the coordination API is currently pinned to the same version. Breaking changes to any "ready" endpoint will bump the major version.

Released under the MIT License.