Python API¶
Embed Landing with the same action contract and executor used by CLI and HTTP. The caller owns the database and runtime lifecycle; enter Runtime.running() while executing work.
Add Landing to your application's environment with uv add "landing==0.1.0". The isolated uv tool install path provides the CLI; embedding uses the package in your application's environment. Configure the model as described in Configuration.
Delegate an action¶
from pathlib import Path
from landing.models import ActionRequest
from landing.runtime import Runtime
async def review():
async with Runtime(Path("landing.sqlite3")).running() as landing:
return await landing.command("review", ActionRequest(
mode="gatekeeper",
instruction="Review the candidate against the acceptance criteria.",
workspace=str(Path.cwd()),
checks=["make acceptance"],
))
Commands select persisted modes: triage selects issuer, fix selects fixer, review selects gatekeeper, and explain selects explainer. Runtime.run() admits a request with its explicit mode. Read the returned action's status, decision, result, and error; exit_code() applies ordinary CLI semantics.
Pass workspaces={"candidate": Path("/srv/candidate")} to select registered names. create_app() accepts the same mapping, token, public origin, skills, and GitHub context for the HTTP service.
Streaming SDK¶
landing.agent.run_stream() accepts native Bub 0.5.0 options: session_id, text or content-part prompt, optional mutable state, per-call model, allowed_tools, allowed_skills, and reasoning_effort.
from contextlib import aclosing
from pathlib import Path
from landing.runtime import Runtime
async def explain():
async with Runtime(Path("landing.sqlite3")).running() as landing:
stream = await landing.agent.run_stream(
session_id="release-question",
prompt=',explain "Explain the failed release check."',
allowed_tools=["fs.read", "bash", "skill"],
allowed_skills=["release-investigation"],
)
async with aclosing(stream):
async for event in stream:
if event.kind == "text":
print(event.data["delta"], end="")
return stream.error, stream.usage
Await the stream, consume it fully, and close it when leaving early. Closing unfinished work requests durable cancellation. Events are native text, reasoning, tool_call, tool_result, usage, error, and final; a final event ends a model step, not necessarily the whole task. Errors and usage remain on the stream. Validation and publication errors also persist in the action record. Native error kinds are retained; other execution failures use unknown.
Model failure logs retain available call metadata and validation error types without argument contents. These diagnostics do not establish the provider as the cause.
The four commands and mode are native agent tools. ,mode reads selection; ,mode gatekeeper selects it without creating a task. Selection persists in the workspace tape and is isolated by session. Content parts stay evidence rather than dispatching commands. Explicit state bypasses hook-based state loading. Per-call tools and skills only narrow mode limits; callers serialize turns within a session.
Hook integration¶
Pass an existing Bub framework to register Landing's business hooks alongside host hooks:
from pathlib import Path
from bub import BubFramework
from bub.channels.message import ChannelMessage
from landing.runtime import Runtime
async def handle():
framework = BubFramework()
framework.load_builtin_hooks()
async with Runtime(Path("landing.sqlite3"), framework=framework).running() as landing:
return await framework.process_inbound(ChannelMessage(
session_id="release-question",
channel="cli",
content=',explain "Explain the failed release check."',
))
The message pipeline retains state, prompt, rendering, and dispatch hooks. Direct SDK calls return events without rendering or dispatching. Both paths share durable tasks and execution. The runtime binds the task workspace; host-provided native environments remain authoritative. Outbound channels belong to the host.
Skills and additional tools¶
Runtime(path, skill_dirs=[...]) and create_app(path, skill_dirs=[...]) add trusted roots with native discovery, skill loading, and $skill-name expansion. Configuration defines precedence.
Pass Bub Tool instances with Runtime(path, tools=[...]), then select them per mode. Authorization belongs in each tool and its execution environment.
Runtime design¶
All four modes share one Bub 0.5.0 agent loop. Landing registers hooks explicitly for mode state, prompts, a task sidecar, and execution. Standalone Landing does not discover external plugins or packaged channel skills.
The sidecar owns actions and action_events. SQLite tape storage in the same database reuses Bub's query and async adapter. Resetting model history does not remove task records; completed tasks do not replay. See Action records and Recovery.
Public objects¶
Bases: Model
Source code in src/landing/models.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
Bases: Model
Source code in src/landing/models.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | |
Source code in src/landing/runtime.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 | |
running()
async
¶
Source code in src/landing/runtime.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
run(request, *, retry_of=None)
async
¶
Source code in src/landing/runtime.py
137 138 139 140 141 142 143 144 145 146 147 148 149 | |
command(name, request, *, session_id='cli', scope='cli', key=None)
async
¶
Delegate a user action through its native command tool.
Source code in src/landing/runtime.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
Source code in src/landing/server.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | |