karta-runtime 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. karta/__init__.py +48 -0
  2. karta/__main__.py +4 -0
  3. karta/active_releases.py +129 -0
  4. karta/agent.py +59 -0
  5. karta/app.py +672 -0
  6. karta/billing/__init__.py +13 -0
  7. karta/billing/usage_emitter.py +416 -0
  8. karta/builder.py +223 -0
  9. karta/byok.py +206 -0
  10. karta/cache.py +257 -0
  11. karta/cli.py +609 -0
  12. karta/cli_renderer.py +173 -0
  13. karta/consumer/__init__.py +7 -0
  14. karta/consumer/challenge.py +116 -0
  15. karta/consumer/embed.py +310 -0
  16. karta/consumer/event_log.py +80 -0
  17. karta/consumer/events.py +34 -0
  18. karta/consumer/managed_agents.py +66 -0
  19. karta/consumer/openai_chat.py +97 -0
  20. karta/consumer/openai_responses.py +116 -0
  21. karta/consumer/project_origins.py +98 -0
  22. karta/consumer/session_token.py +78 -0
  23. karta/control_plane.py +140 -0
  24. karta/durable/__init__.py +22 -0
  25. karta/durable/local.py +34 -0
  26. karta/durable/port.py +19 -0
  27. karta/durable/s3.py +64 -0
  28. karta/events.py +21 -0
  29. karta/execution/__init__.py +22 -0
  30. karta/execution/fake.py +78 -0
  31. karta/execution/local.py +96 -0
  32. karta/execution/port.py +85 -0
  33. karta/gateway/__init__.py +15 -0
  34. karta/gateway/base.py +29 -0
  35. karta/gateway/events.py +73 -0
  36. karta/gateway/http.py +80 -0
  37. karta/gateway/local.py +151 -0
  38. karta/gateway/registry.py +65 -0
  39. karta/harness/__init__.py +4 -0
  40. karta/harness/base.py +142 -0
  41. karta/harness/claude.py +974 -0
  42. karta/harness/claude_message_mapper.py +418 -0
  43. karta/harness/factory.py +37 -0
  44. karta/harness/opencode.py +592 -0
  45. karta/harness/utils.py +45 -0
  46. karta/hub.py +126 -0
  47. karta/instance.py +729 -0
  48. karta/model_settings.py +134 -0
  49. karta/observability/__init__.py +156 -0
  50. karta/observability/_noop.py +75 -0
  51. karta/observability/_otel.py +81 -0
  52. karta/observability/logging_setup.py +96 -0
  53. karta/observability/metrics.py +74 -0
  54. karta/observability/secret_redaction.py +96 -0
  55. karta/participant.py +55 -0
  56. karta/persistence/__init__.py +46 -0
  57. karta/persistence/gc.py +164 -0
  58. karta/persistence/local_backend.py +128 -0
  59. karta/persistence/manifest.py +225 -0
  60. karta/persistence/profile.py +156 -0
  61. karta/persistence/reconcile.py +76 -0
  62. karta/persistence/s3_backend.py +131 -0
  63. karta/persistence/store.py +297 -0
  64. karta/persistence/text_merge.py +70 -0
  65. karta/policies.py +29 -0
  66. karta/project_runtime.py +201 -0
  67. karta/registry.py +137 -0
  68. karta/release_artifacts.py +70 -0
  69. karta/releases.py +478 -0
  70. karta/response.py +150 -0
  71. karta/runtime/__init__.py +3 -0
  72. karta/runtime/turn_accumulator.py +146 -0
  73. karta/secrets_bootstrap.py +88 -0
  74. karta/server/__init__.py +3 -0
  75. karta/server/agentcore.py +393 -0
  76. karta/server/agentcore_client.py +481 -0
  77. karta/server/agentcore_project_runtime.py +210 -0
  78. karta/server/agentcore_router.py +118 -0
  79. karta/server/agentcore_serve.py +116 -0
  80. karta/server/auth.py +625 -0
  81. karta/server/credential_guard.py +51 -0
  82. karta/server/fastapi_app.py +2454 -0
  83. karta/server/instance_routes.py +199 -0
  84. karta/server/serve_surfaces.py +231 -0
  85. karta/server/session_store.py +220 -0
  86. karta/server/sse.py +10 -0
  87. karta/session.py +161 -0
  88. karta/vault/__init__.py +21 -0
  89. karta/vault/agentcore.py +33 -0
  90. karta/vault/local.py +43 -0
  91. karta/vault/port.py +28 -0
  92. karta/workspace.py +489 -0
  93. karta_runtime-0.1.0.dist-info/METADATA +396 -0
  94. karta_runtime-0.1.0.dist-info/RECORD +96 -0
  95. karta_runtime-0.1.0.dist-info/WHEEL +4 -0
  96. karta_runtime-0.1.0.dist-info/entry_points.txt +3 -0
karta/__init__.py ADDED
@@ -0,0 +1,48 @@
1
+ from karta.agent import Agent
2
+ from karta.app import Karta
3
+ from karta.gateway import (
4
+ Gateway,
5
+ GatewayDelivery,
6
+ GatewayEvent,
7
+ HttpGateway,
8
+ LocalGateway,
9
+ ParticipantRegistry,
10
+ )
11
+ from karta.hub import KartaHub
12
+ from karta.instance import AgentInstance, InstanceManager, InstanceSession, InstanceStore
13
+ from karta.participant import AIAgent, HumanAgent, Participant
14
+ from karta.policies import PolicyViolationError
15
+ from karta.registry import ProjectRegistry, PublishedProject
16
+ from karta.response import Message, Part, Response, StreamEvent, Usage
17
+ from karta.session import Session
18
+ from karta.workspace import UserProjectView, WorkspaceManager
19
+
20
+ __all__ = [
21
+ "AgentInstance",
22
+ "Karta",
23
+ "Agent",
24
+ "AIAgent",
25
+ "Gateway",
26
+ "GatewayDelivery",
27
+ "GatewayEvent",
28
+ "HttpGateway",
29
+ "HumanAgent",
30
+ "InstanceManager",
31
+ "InstanceSession",
32
+ "InstanceStore",
33
+ "KartaHub",
34
+ "LocalGateway",
35
+ "Message",
36
+ "Part",
37
+ "Participant",
38
+ "ParticipantRegistry",
39
+ "PolicyViolationError",
40
+ "ProjectRegistry",
41
+ "PublishedProject",
42
+ "Response",
43
+ "Session",
44
+ "StreamEvent",
45
+ "Usage",
46
+ "UserProjectView",
47
+ "WorkspaceManager",
48
+ ]
karta/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from karta.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,129 @@
1
+ """Active-release resolution for the data plane.
2
+
3
+ When a session is created for a project URL, the data plane must learn
4
+ WHICH materialized release to run. karta-web is authoritative: it owns
5
+ the ``AgentProject#current_release`` pointer that the deploy/activate
6
+ flow flips. This module fetches that pointer for an ``(org, project)``
7
+ pair and caches it briefly so the hot path doesn't round-trip the
8
+ control plane on every session create.
9
+
10
+ Mirrors :mod:`karta.model_settings` in shape (cached control-plane GET,
11
+ ``OrgScopedTtlCache`` keyed by org first so push-invalidation matches).
12
+ The returned :class:`ActiveRelease` carries the project's numeric id and
13
+ version, which :mod:`karta.project_runtime` turns into a deterministic
14
+ on-disk template path via :func:`karta.releases.template_path_for`.
15
+
16
+ Nothing here is secret: a template path / commit sha / version is
17
+ operational metadata already surfaced in dashboards, so no scrubbing.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from dataclasses import dataclass
23
+ from typing import Optional
24
+ from urllib.parse import quote
25
+
26
+ from karta import observability as obs
27
+ from karta.cache import OrgScopedTtlCache, env_number
28
+ from karta.control_plane import ControlPlaneUnavailable, control_plane_get
29
+
30
+ logger = obs.get_logger("karta.active_releases")
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class ActiveRelease:
35
+ """The project's currently-active release, as resolved by karta-web."""
36
+
37
+ project_id: int
38
+ version: int
39
+ build_kind: str
40
+ template_path: Optional[str]
41
+ commit_sha: Optional[str]
42
+ status: Optional[str] = None
43
+ image_ref: Optional[str] = None
44
+
45
+
46
+ # Keyed by (organization_id, project_ref) so invalidate_org matches on
47
+ # the first tuple element. TTL is short by default: the cached value
48
+ # carries the active version, so a longer window means a freshly
49
+ # activated release keeps serving the OLD version until expiry. A new
50
+ # session is cheap to mis-route only within this window; in-flight
51
+ # sessions are pinned to their start version regardless (see
52
+ # project_runtime). karta-web push-invalidates on activate when wired.
53
+ _CACHE_TTL_SECONDS = env_number("KARTA_ACTIVE_RELEASE_CACHE_TTL", 10.0, float)
54
+ _cache: OrgScopedTtlCache[ActiveRelease] = OrgScopedTtlCache(_CACHE_TTL_SECONDS)
55
+
56
+
57
+ def reset_active_release_cache() -> None:
58
+ """Test/ops hook: drop all cached active-release entries."""
59
+ _cache.clear()
60
+
61
+
62
+ def invalidate_active_release_for_org(organization_id: int) -> int:
63
+ """Drop every cached active-release entry for ``organization_id``.
64
+
65
+ Called when karta-web posts a CacheInvalidationJob after a release
66
+ is activated or rolled back. Returns the number of entries evicted.
67
+ """
68
+ return _cache.invalidate_org(organization_id)
69
+
70
+
71
+ def fetch_active_release(
72
+ *,
73
+ organization_id: int,
74
+ project_ref: str,
75
+ control_plane_url: Optional[str] = None,
76
+ service_token: Optional[str] = None,
77
+ ) -> Optional[ActiveRelease]:
78
+ """Return the active release for ``(organization_id, project_ref)``.
79
+
80
+ ``project_ref`` is the project slug from the project URL. Returns
81
+ ``None`` when the project has no active release, is unknown, or the
82
+ control plane is unreachable. The two are distinguished by caching:
83
+ a definitive miss (404 / non-200) is negative-cached; an outage
84
+ (:class:`ControlPlaneUnavailable`) is not, so the next create retries.
85
+ """
86
+ key = (organization_id, project_ref)
87
+ hit, cached = _cache.get(key)
88
+ if hit:
89
+ return cached
90
+
91
+ path = f"/internal/projects/{quote(project_ref, safe='')}/active_release"
92
+ try:
93
+ payload = control_plane_get(
94
+ path,
95
+ params={"organization_id": organization_id},
96
+ control_plane_url=control_plane_url,
97
+ service_token=service_token,
98
+ )
99
+ except ControlPlaneUnavailable:
100
+ return None
101
+
102
+ if payload is None:
103
+ _cache.put(key, None)
104
+ return None
105
+
106
+ project_id = payload.get("project_id")
107
+ version = payload.get("version")
108
+ if not isinstance(project_id, int) or not isinstance(version, int):
109
+ # Malformed answer — treat as a definitive miss rather than
110
+ # constructing a half-formed release we can't resolve a path for.
111
+ logger.warning(
112
+ "active_release for org=%s project=%s missing project_id/version",
113
+ organization_id,
114
+ project_ref,
115
+ )
116
+ _cache.put(key, None)
117
+ return None
118
+
119
+ release = ActiveRelease(
120
+ project_id=project_id,
121
+ version=version,
122
+ build_kind=str(payload.get("build_kind") or "file_copy"),
123
+ template_path=(str(payload["template_path"]) if payload.get("template_path") else None),
124
+ commit_sha=(str(payload["commit_sha"]) if payload.get("commit_sha") else None),
125
+ status=(str(payload["status"]) if payload.get("status") else None),
126
+ image_ref=(str(payload["image_ref"]) if payload.get("image_ref") else None),
127
+ )
128
+ _cache.put(key, release)
129
+ return release
karta/agent.py ADDED
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Optional
4
+
5
+ from karta.harness.base import AgentDefinition
6
+ from karta.response import Response, StreamEvent
7
+
8
+ if TYPE_CHECKING:
9
+ from karta.app import Karta
10
+
11
+
12
+ class Agent:
13
+ def __init__(self, app: "Karta", definition: AgentDefinition) -> None:
14
+ self._app = app
15
+ self.definition = definition
16
+
17
+ @property
18
+ def name(self) -> str:
19
+ return self.definition.name
20
+
21
+ async def send(
22
+ self,
23
+ text: str,
24
+ *,
25
+ metadata: Optional[Dict[str, Any]] = None,
26
+ thinking: bool = False,
27
+ ) -> Response:
28
+ return await self._app.send(text, metadata=metadata, agent=self, thinking=thinking)
29
+
30
+ def send_sync(
31
+ self,
32
+ text: str,
33
+ *,
34
+ metadata: Optional[Dict[str, Any]] = None,
35
+ thinking: bool = False,
36
+ ) -> Response:
37
+ return self._app.send_sync(text, metadata=metadata, agent=self, thinking=thinking)
38
+
39
+ async def stream(
40
+ self,
41
+ text: str,
42
+ *,
43
+ metadata: Optional[Dict[str, Any]] = None,
44
+ thinking: bool = False,
45
+ ) -> AsyncIterator[StreamEvent]:
46
+ async for event in self._app.stream(text, metadata=metadata, agent=self, thinking=thinking):
47
+ yield event
48
+
49
+ def stream_sync(
50
+ self,
51
+ text: str,
52
+ *,
53
+ metadata: Optional[Dict[str, Any]] = None,
54
+ thinking: bool = False,
55
+ ):
56
+ return self._app.stream_sync(text, metadata=metadata, agent=self, thinking=thinking)
57
+
58
+ def __repr__(self) -> str:
59
+ return f"Agent(name={self.name!r})"