langstage-core
The shared core behind the LangStage family: a host layer for LangGraph agents (spec-loading + layered config), an in-process AG-UI bridge that streams any CompiledGraph to a frontend, an async task-delegation engine, and interrupt-aware input helpers. Write your agent once — any LangGraph CompiledGraph — and every LangStage surface runs it the same way.
1.0 — renamed from
langgraph-stream-parser. The oldStreamParser/events/event_to_dictevent layer was retired in favor of the AG-UI wire (see Migrating and ADR 0003). The oldimport langgraph_stream_parserkeeps working as long as the separatelanggraph-stream-parsercompat package stays installed (it re-exportslangstage_core); a fresh install oflangstage-corealone does not provide it.
Every stage for your LangGraph agent
langstage-core is the shared core of the LangStage family: write your agent once — any LangGraph CompiledGraph — and run it on every stage with the same spec string (module:attr or path/to/file.py:attr), the same langstage.toml config file, and the same LANGSTAGE_* environment variables. (The pre-rename deepagents.toml / DEEPAGENT_* vocabulary still resolves as a deprecated fallback.)
| Stage | Package | Try it |
|---|---|---|
| Web app | langstage | langstage run --agent my_agent.py:graph |
| JupyterLab | langstage-jupyter | pip install langstage-jupyter, then the chat sidebar in jupyter lab |
| Terminal | langstage-cli | langstage-cli -a my_agent.py:graph |
| VS Code | langstage-vscode | chat participant + stdio sidecar |
| Reference agent | langstage-hermes | LANGSTAGE_AGENT_SPEC=langstage_hermes.agent:graph on any stage |
| Shared core | langstage-core | you are here |
📖 Full documentation: https://dkedar7.github.io/langstage-docs/
Installation
pip install "langstage-core[agui]"
The [agui] extra pulls the AG-UI runtime (ag-ui-langgraph[fastapi] + uvicorn) — needed for the streaming bridge below and by every LangStage surface. The bare pip install langstage-core (only langchain-core) covers the host/config layer only (load_agent_spec, HostConfig, the resume helpers). The task engine needs [agui] too: SessionAdapter drives every task through the AG-UI bridge, so on a bare install each task ends failed.
No agent of your own yet? The [stub] extra adds a keyless echo graph you can stream:
pip install "langstage-core[agui,stub]"
Quick start
Wrap any compiled graph with build_agent, then stream a turn. Two shared mappings cover the two frontend styles the family uses:
import asyncio
from langstage_core import load_agent_spec
from langstage_core.agui import build_agent, iter_event_frames
# any LangGraph CompiledGraph — here the keyless demo stub
agent = build_agent(load_agent_spec("langstage_core.demo.stub:graph"))
async def main():
async for frame in iter_event_frames(agent, "hello", thread_id="s1"):
if frame["type"] == "content":
print(frame["content"], end="")
elif frame["type"] == "complete":
print()
asyncio.run(main())
iter_event_framesyields rich, typed frames —content,tool_start,tool_end,reasoning,interrupt,extraction,complete,error— used by the web and VS Code surfaces.iter_chunk_framesyields terminal-friendlystatus-keyed chunk dicts — used by the CLI and Jupyter surfaces. Astreamingchunk carries exactly one payload key (chunk,reasoning,tool_calls,tool_result, orextraction), so branch on the key rather than assumingchunk.
build_agent attaches an in-memory checkpointer if the graph has none (on a copy — your graph object is never mutated), so multi-turn memory and interrupts work out of the box: build the agent once, reuse it, and pass a thread_id per turn to key per-conversation state.
Frame reference
Both wires carry the same information; the table is the contract (keys marked new are additive and safe to ignore).
Event wire (iter_event_frames) |
Chunk wire (iter_chunk_frames) |
Meaning |
|---|---|---|
{"type": "content", "content", "role", "node", "message_id"} |
{"status": "streaming", "chunk", "node", "message_id"} |
Assistant text delta. message_id (new) is the AIMessage it belongs to: a change of id between two text frames is a message boundary (e.g. two nodes' replies) — join with a paragraph break, not inline. |
{"type": "reasoning", "content", "node"} |
{"status": "streaming", "reasoning", "node"} |
Reasoning-model chain-of-thought, separate from the answer. |
{"type": "tool_start", "id", "name", "args", "node"} |
{"status": "streaming", "tool_calls": [{"name", "args", "id"}]} |
A tool call (chunk id is new). |
{"type": "tool_end", "id", "name", "result", "status", "error_message", "duration_ms"} |
{"status": "streaming", "tool_result", "id", "name", "tool_status", "duration_ms"} |
A tool result (capped at max_result_len). status / tool_status is "success" or "error"; duration_ms is the tool's run time, or None when the tool ran outside LangChain's tool runtime (e.g. a hand-written node). Chunk id / name / tool_status / duration_ms are new; tool_result is still the result string. |
{"type": "extraction", "tool_name", "extracted_type", "data"} |
{"status": "streaming", "extraction": {"tool_name", "extracted_type", "data"}} |
An extractor's output for a successful tool result (never emitted for a failed tool). |
{"type": "interrupt", "action_requests", "review_configs", "allowed_decisions"} |
{"status": "interrupt", "interrupt": {...same keys}} |
A HITL pause; resume with resume=. |
{"type": "complete", "outcome"} |
{"status": "complete", "outcome"} |
Terminal. outcome (new) is "interrupted" if the turn paused on an interrupt, else "complete". |
{"type": "error", "error"} |
{"status": "error", "error"} |
Terminal — nothing follows it (no complete). Content earlier nodes already produced is emitted before it. |
Frames arrive in message order: a node that returns a finished AIMessage (no token streaming — model.invoke(), a router, a canned reply) is emitted when that node finishes, its text before its own tool calls, and the served AG-UI endpoint (build_app / serve) streams the same TEXT_MESSAGE_* / TOOL_CALL_* events the in-process wires are built from.
See every frame type, keyless
The echo stub above only emits content. To see the rich frames without an API key, point build_agent at the bundled tool demo (langstage_core.demo.tools:graph): it calls a built-in tool through a real ToolNode, streams a reasoning delta, and raises a resumable interrupt, all deterministically and offline. Each trigger phrase drives a different frame type:
import asyncio
from langstage_core import create_resume_input
from langstage_core.agui import build_agent, iter_event_frames
from langstage_core.demo.tools import create_tool_demo_agent, demo_extractors
agent = build_agent(create_tool_demo_agent())
async def main():
for turn in ("hello", "think about it", "use a tool"):
async for frame in iter_event_frames(agent, turn, "s1", extractors=demo_extractors()):
print(frame["type"], "→", {k: v for k, v in frame.items() if k != "type"})
# "ask me" raises interrupt(...); resume the same thread with a decision.
async for frame in iter_event_frames(agent, "ask me", "s2"):
print(frame["type"]) # ... interrupt, complete (outcome="interrupted")
async for frame in iter_event_frames(agent, "", "s2",
resume=create_resume_input(decisions=[{"type": "approve"}])):
print(frame["type"]) # content, complete
asyncio.run(main())
# content · reasoning · tool_start · tool_end · extraction · interrupt · complete
Serve the same demo over AG-UI with langstage-agui --demo=tools.
One call, one answer (no streaming)
The iter_* mappings are streaming generators — perfect for a live UI, but a test, an eval/grading harness, a batch job, or a "run my agent once, give me the answer" script wants a single call that returns the result. run_turn (sync) / collect_event_frames (async) do exactly that, returning a typed TurnResult (text, tool_calls, extractions, reasoning, outcome, interrupt, error, and frames — an int frame count, not the frame list) — nothing streamed, nothing hand-accumulated:
from langstage_core.agui import run_turn
from langstage_core.demo.tools import create_tool_demo_agent, demo_extractors
result = run_turn(create_tool_demo_agent(), "use a tool", extractors=demo_extractors())
result.text # 'The demo tool returned {"query": "use a tool", "answer": "42", ...}'
result.tool_calls # [{'name': 'demo_lookup', 'args': {'query': 'use a tool'}, 'id': 'demo_lookup_1'}]
result.extractions # [{'tool_name': 'demo_lookup', 'extracted_type': 'demo_fact', 'data': {...}}]
result.outcome # 'complete' ('interrupted' on "ask me", 'error' on a failing turn)
run_turn accepts a compiled graph or a prebuilt build_agent(...) and runs the turn under asyncio.run. Each call is an isolated one-shot by default — a fresh thread_id per call, and the graph you pass is not mutated — so for prompt in dataset: run_turn(graph, prompt) never leaks one turn into the next; to carry state across calls (or resume an interrupt), pass the same build_agent(...) agent and an explicit thread_id each time; inside an event loop, await collect_event_frames(agent, message, thread_id, ...) instead (or collect_chunk_frames for the chunk wire). The complete / interrupted / error verdict is the same rule SessionAdapter uses, so a one-shot turn and a streamed one agree. (The sibling langstage package's oneturn.py is a different layer — it buffers a SessionAdapter for the web one-turn HTTP endpoint; these core helpers are session-free, for tests/evals/scripts.)
Connect a real model
The demos above are keyless. To stream your own model-backed agent, bring any LangGraph CompiledGraph — nothing about the library is demo-specific. The [real] extra pulls a lightweight OpenAI-compatible stack:
pip install "langstage-core[agui,real]" # langchain-openai + langchain + langgraph
import asyncio, os
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langstage_core.agui import build_agent, iter_event_frames
# Works with OpenAI, OpenRouter, or any OpenAI-compatible endpoint:
model = ChatOpenAI(
model="gpt-4o-mini",
base_url=os.environ.get("OPENAI_BASE_URL"), # e.g. https://openrouter.ai/api/v1
api_key=os.environ["OPENAI_API_KEY"],
)
agent = build_agent(create_agent(model, tools=[]))
async def main():
async for frame in iter_event_frames(agent, "Say hi in one word.", thread_id="s1"):
if frame["type"] == "content":
print(frame["content"], end="")
asyncio.run(main())
Everything else — run_turn, serve, the task engine, extractors — takes the same build_agent(...) agent, so the keyless snippets above work verbatim against a real model once you swap the graph. (create_agent is LangChain 1.x's agent builder; LangGraph's create_react_agent is deprecated since LangGraph 1.0.) (Prefer Anthropic + the full agent stack? pip install deepagents langchain-anthropic and build a deepagents graph instead; the library only ever sees a CompiledGraph.)
Delegate work to a background task
The task engine is a single-process worker pool: enqueue a prompt, walk away, and read the result off the board when it's done. Any CompiledGraph drives the workers (install [agui]: the workers run on the AG-UI bridge).
import asyncio
from langstage_core import SessionAdapter
from langstage_core.demo.tools import create_tool_demo_agent
from langstage_core.tasks import REVIEW_NEEDED, TERMINAL_STATES, InMemoryTaskStore, TaskRunner
async def main():
adapter = SessionAdapter(graph=create_tool_demo_agent()) # keyless; "ask me" interrupts
runner = TaskRunner(adapter, InMemoryTaskStore(), concurrency=3)
await runner.start()
task_id = await runner.enqueue(title="research", prompt="ask me first")
# Poll until the task is terminal. A HITL agent parks at review_needed, which is
# NOT terminal: nothing moves it on until a human answers with runner.resume().
while (task := await runner.store.get(task_id))["state"] not in TERMINAL_STATES:
if task["state"] == REVIEW_NEEDED:
print(task["interrupt"]["allowed_decisions"]) # ['respond', 'approve']
await runner.resume(task_id, [{"type": "approve"}]) # a bare list of decisions
await asyncio.sleep(0.1)
print(task["state"]) # 'done'
print(task["result"]) # the agent's answer
await runner.shutdown()
asyncio.run(main())
A Task is a TypedDict — read it with task["state"] / task["result"] / task["error"] / task["interrupt"], not attribute access. States flow queued → ongoing → review_needed → done | failed | cancelled. TERMINAL_STATES (done / failed / cancelled) is the set to stop polling on, but a task only gets there unattended if its agent never interrupts: review_needed waits for a human, so a loop that only checks TERMINAL_STATES spins forever on a HITL agent. Handle it as above, or stop polling at review_needed and resume later.
Driving a task after enqueue (each returns False when the task isn't in a state that allows it):
await runner.resume(task_id, decisions): answer areview_neededtask.decisionsis the decision list ([{"type": "approve"}]); the{"decisions": [...]}envelope is accepted too.await runner.followup(task_id, message): continue a finished (done/failed/cancelled) task's thread with a new message.await runner.retry(task_id): re-run afailedorcancelledtask.await runner.cancel(task_id): stop a queued or running task.
TASK_TOOLS (with set_runner / get_runner) are the agent-facing delegation tools, so an agent can enqueue background work to copies of itself.
Human-in-the-loop (interrupt → resume)
When the graph calls interrupt(...), you get an interrupt frame; resume by passing the decision back via resume=:
async for frame in iter_event_frames(agent, "run it", thread_id="s1"):
if frame["type"] == "interrupt":
# frame["action_requests"], frame["allowed_decisions"]
...
# next turn resumes the same thread with the user's decision
async for frame in iter_event_frames(agent, "", thread_id="s1",
resume={"decisions": [{"type": "approve"}]}):
...
Decision verbs. Core uses one vocabulary, LangChain's HumanInTheLoopMiddleware verbs:
Core advertises (allowed_decisions) |
Legacy LangGraph HumanInterrupt equivalent |
Accepted on resume as an alias |
|---|---|---|
approve |
allow_accept / accept |
accept |
edit |
allow_edit / edit |
none (same word) |
reject |
allow_ignore / ignore |
ignore |
respond |
allow_respond / response |
response |
- Advertised:
frame["allowed_decisions"]is the interrupt's own decision set, always in the left-hand vocabulary. A HumanInTheLoopMiddleware payload's per-actionreview_configs[*].allowed_decisionsor a legacy HumanInterrupt'sconfig(mapped by the table) decide it, so an approve-only interrupt advertises exactly["approve"]. All four are the fallback only when the interrupt says nothing. - Accepted: the helpers below take either vocabulary, in any case. On
resume=to a HumanInTheLoopMiddleware request, a decisiontypemay be the canonical verb or its alias. - Translated: when the pending interrupt is a HumanInTheLoopMiddleware request (an
action_requestspayload), core rewrites each alias in the{"decisions": [...]}envelope to its canonical verb, which is what the middleware reads (it raises onaccept). Any other interrupt, such as a legacy HumanInterrupt list or your owninterrupt(...), gets the payload verbatim, because that graph reads its own vocabulary. The envelope itself is never reshaped.
Core does not refuse a disallowed verb on resume; the surface should, before it resumes. normalize_decision / is_allowed_decision (top-level) check a verb against the pending interrupt, aliases included; DECISION_VERBS and DECISION_ALIASES hold the table:
from langstage_core import is_allowed_decision, normalize_decision
allowed = ["reject", "approve"] # frame["allowed_decisions"]
normalize_decision("accept", allowed) # 'approve' (alias -> canonical)
normalize_decision("edit", allowed) # None (not allowed here: refuse it)
is_allowed_decision("ignore", allowed) # True (ignore == reject)
resume= takes the raw payload or a create_resume_input(...) Command. On ag-ui-langgraph ≥ 0.0.43 it is sent on the adapter's standard RunAgentInput.resume[] (answering the thread's pending interrupt), so a resume logs no forwardedProps.command.resume is deprecated / failed to parse … resume_input as JSON warning; older adapters, or a thread with several pending interrupts, keep the legacy forwarded_props.command.resume wire.
What's in the box
Everything is re-exported from the top-level langstage_core package (except the AG-UI helpers under langstage_core.agui):
| Area | API | What it does |
|---|---|---|
| Host | load_agent_spec, HostConfig, Workspace |
Load a graph from a module:attr / file.py:attr spec; resolve layered config (defaults < langstage.toml < LANGSTAGE_* env < overrides). |
AG-UI bridge (langstage_core.agui) |
build_agent, iter_event_frames, iter_chunk_frames, collect_event_frames / collect_chunk_frames / run_turn (→ TurnResult), build_app, serve, add_agui_endpoint |
Stream any CompiledGraph in-process (the iter_* mappings), collect one turn into a typed TurnResult (the collect_* / run_turn one-shots), or serve it as an AG-UI HTTP endpoint. |
Session adapter (top-level; also langstage_core.adapters) |
SessionAdapter, Session |
A session-scoped driver over the AG-UI agent with a typed terminal outcome — the streaming engine behind the web app + task board. |
| Input helpers | prepare_agent_input, create_resume_input, normalize_decision, is_allowed_decision |
Build graph input from a message (+ optional context) or a resume decision; check a decision verb against an interrupt's allowed_decisions. |
| Extractors | ToolExtractor + built-ins (ThinkToolExtractor, TodoExtractor, DisplayInlineExtractor, SkillManageExtractor, MemoryExtractor, …) |
Turn a tool's result into a structured extraction frame; pass extractors=[...] to the iter_* mappings. |
| Task engine | TaskRunner, TaskStore, InMemoryTaskStore, TASK_TOOLS, set_runner, get_runner |
Async delegate-and-walk-away worker pool (enqueue, resume, followup, retry, cancel) + a persistence-agnostic store Protocol; TASK_TOOLS are the agent-facing delegation tools. |
Serve any agent over AG-UI
Any LangGraph agent can be served over the AG-UI protocol — the event-based wire for streaming rich agent interactions (text, tool calls, reasoning, state, interrupts) to frontends (CopilotKit, React/Vue/Angular components, any AG-UI client). The host layer resolves which agent; the official MIT ag-ui-langgraph adapter owns the wire:
langstage-agui --agent my_agent.py:graph # serve over AG-UI at http://localhost:8050
langstage-agui --demo # keyless echo agent, no API key
langstage-agui --demo=tools # keyless rich-frame demo (tools, reasoning, interrupt)
langstage-agui --agent my_agent.py:graph --verify # run one keyless turn; exit 0 ok / 1 failed
langstage-agui --agent my_agent.py:graph -m "hi there" # run ONE turn with your prompt, print the reply
--verify is the preflight to run right after wiring up an agent: --show-config proves the config chain resolves a spec, but --verify proves it loads and actually produces a turn — catching the two most common failures (a typo'd module:attr, or a graph that loads but yields an empty/erroring turn) that otherwise only surface at first chat. Keyless, so it fits a CI/deploy gate. --message/-m is its companion — run one turn with your prompt and print the answer (add --json for the typed TurnResult), exit 0/1/2 on complete/error/interrupt. The three questions every adopter asks, in order: --show-config (resolves?) → --verify (runs?) → --message (what does it say?).
Every can't-run failure is a clean one-line error: on stderr, never a traceback (set LANGSTAGE_DEBUG=1 for one): an agent that loads but isn't a runnable graph (e.g. a StateGraph you forgot to .compile()) exits 1 under both --verify and --message (--json still prints a typed TurnResult with outcome: "error"). When serving, the port is bound before the Serving … at <url> banner prints, so a port already in use is error: cannot serve at <url>: … and exit 2 (the serve path's can't-start code, like an unloadable spec), not a success banner followed by a crash. serve() binds first too and raises OSError for a busy port.
from langstage_core.agui import build_app
app = build_app(my_compiled_graph) # an ASGI (FastAPI) app; run with uvicorn
Browser frontends on another origin (CORS). The server sends no CORS headers by default, so only same-origin pages and non-browser clients can call it. A frontend dev server on another port (http://localhost:5173 calling http://localhost:8050) needs an opt-in allowlist:
langstage-agui --demo=tools --cors # any localhost / 127.0.0.1 / [::1] origin
langstage-agui --demo=tools --cors http://localhost:5173 # exactly these origins (comma-separated)
app = build_app(my_compiled_graph, cors_origins=["https://app.example.com"]) # or "loopback"
serve(my_compiled_graph, cors_origins="loopback")
"*" is honored only if you pass it explicitly; it is never a default. Credentials (cookies) are not allowed cross-origin.
See ADR 0001 for the rationale.
Configuration
The same resolution chain everywhere — defaults < langstage.toml < LANGSTAGE_* env < CLI/overrides (legacy deepagents.toml / DEEPAGENT_* still resolve as a deprecated fallback). Print the resolved value + source of every shared key:
python -m langstage_core.host # every shared key
langstage-agui --show-config # each surface's --show-config: the keys that surface uses
A surface's --show-config leaves out keys it ignores, and says so on a (not used by this surface, so not shown: ...) line. langstage-agui omits workspace_root and title; --show-config --json lists them under omitted.
What the diagnostic tells you:
- Every contributing file.
TOML read from:lists the global~/.langstage/config.tomland the projectlangstage.toml;config_dict()["toml"]["paths"]is the same list as data. - A malformed file is reported as malformed, not missing. A
langstage.tomlthat doesn't parse is ignored entirely (every key falls back to env/defaults) and shows asTOML: <path> is MALFORMED and was ignored entirely (<parse error>). Inconfig_dict()it appears astoml.found: true,toml.malformed: true, andtoml.malformed_files: [{path, error}]. - Anything ignored or degraded, as data.
HostConfig.config_issues()(andconfig_dict()["issues"]) lists each malformed file, each wrong-type or invalid value that fell back to a default, and each unknown key. An empty list means the config is clean, so a surface's--strictgate can fail when the list isn't empty. debugis a top-level key. In TOML,debug = truemust come before the first[table]header. Written below[server], TOML reads it asserver.debug. That key is ignored, and anote:saying so is printed at startup.- A rejected value falls back one layer, not to the default. A value that fails a check (an out-of-range
LANGSTAGE_PORT, say) is ignored with anote:, and the next layer down is used: thelangstage.tomlvalue if one is set, else the default. - Booleans accept
true/false,0/1, and the same quoted strings as env vars ("yes","off", ...). An unrecognized value falls back to the default and prints anote:. [configurable]keys are passed to the graph'sconfig["configurable"]bylangstage-agui(for both serving and--message), and--show-configlists them.thread_idis always set per run. Python callers passbuild_agent(config=...)themselves.- Legacy names (
DEEPAGENT_*,DEEPAGENTS_CONFIG_HOME,deepagents.toml) each print exactly onenote:per process. SetLANGSTAGE_SUPPRESS_LEGACY_NOTICE=1to silence them.
Surfaces print user-controlled values, so they should print through langstage_core.console.safe_print / safe_write. These escape characters the console can't encode (a cp1252 Windows console, for example) instead of raising UnicodeEncodeError.
Agent specs and relative paths
A spec is path/to/file.py:attr or package.module:attr. The :attr suffix is required: a colon-less spec is an error, never a silent fallback to a default agent. Surrounding whitespace is ignored and a leading ~ is expanded. The attribute must be the agent object itself: a str attribute is rejected, not followed as another spec. load_agent_spec imports like python my_agent.py does:
file.py:attrputs the file's own directory first onsys.path, so the agent can import its sibling modules (from tools import ...).package.module:attrfalls back to the current directory (orbase_dir=) when the package isn't otherwise importable. That covers a project-local package run from a console script.load_agent_spec(spec, stdout_to_stderr=True)sends the agent's import-timeprints to stderr. Use it on machine-readable paths.langstage-agui --verify/-m/--jsonalready do.
Relative paths in a TOML file resolve against that file's directory, like paths in pyproject.toml. This applies to [agent] spec (file form) and [workspace] root, for both the project langstage.toml (found by walking up from the cwd) and the global ~/.langstage/config.toml. A project therefore runs the same from its root and from any subdirectory. In the global file, relative paths resolve against ~/.langstage/, so write ~/agents/my_agent.py:graph or an absolute path there. Values from LANGSTAGE_* env vars and CLI flags stay relative to the cwd. For a dotted spec from TOML, cfg.toml_dir_for("agent_spec") gives you the file's directory to pass as base_dir=.
Migrating from langgraph-stream-parser
langstage-core 1.0 is the rename of langgraph-stream-parser. The old import name keeps working through a separate compat package — langgraph-stream-parser 1.0, which now just re-exports langstage_core (with a DeprecationWarning). So import langgraph_stream_parser and its submodules keep resolving only while that package remains installed:
- Upgrading in place (
pip install -U langgraph-stream-parser) → you keep the shim package, so the old import keeps working. Update toimport langstage_corewhen convenient. - Installing
langstage-corefresh does not pull the shim (it's a separate distribution, and depending on it would be circular). Eitherimport langstage_core(recommended), orpip install langgraph-stream-parseralongside if you need the old name during a transition.
The event layer was removed in 1.0. If you used it directly, migrate:
| Removed (pre-1.0) | Use instead |
|---|---|
StreamParser, langstage_core.events, event_to_dict |
langstage_core.agui.iter_event_frames / iter_chunk_frames (frame dicts, same vocabulary) |
stream_graph_updates, resume_graph_from_interrupt |
iter_chunk_frames(agent, msg, thread_id, resume=...) |
adapters.CLIAdapter / PrintAdapter / FastAPIAdapter / JupyterDisplay |
SessionAdapter (in-process) or build_app / serve (HTTP), both AG-UI |
Kept and unchanged: load_agent_spec, HostConfig, prepare_agent_input, create_resume_input, the tasks engine, and the extractors (ToolExtractor + built-ins). Full detail: ADR 0003.
Development
pip install -e ".[dev]"
pytest
pytest --cov=langstage_core
License
MIT
Release files for langstage-core 1.0.37
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| langstage_core-1.0.37.tar.gz | 374.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| langstage_core-1.0.37-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 495.7 kB
Release files / langstage_core-1.0.37.tar.gz
| Download URL | langstage_core-1.0.37.tar.gz |
|---|---|
| Size | 374.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
91df853af4fdf01fd06dc62e3cd9f3fbbe7835879e9016812482ba53b2bc2221
|
|
BLAKE2b-256 checksum How to use checksums |
8cab2d23080c291f7b887a3385c1cf73173772412fb719339c4d91e98bdaaf3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / langstage_core-1.0.37-py3-none-any.whl
| Download URL | langstage_core-1.0.37-py3-none-any.whl |
|---|---|
| Size | 121.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b43703a023d2455ea9f3018324816bb797c14e47025704fdfc20ae00d9546a55
|
|
BLAKE2b-256 checksum How to use checksums |
5420255ef6e37192d892a3b54e3ab3c08616d9ba1489088dbe62dbc76e0ea30c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|