Compile a declarative DAG of subagents into an orchestrated team on AWS Bedrock AgentCore.
Project description
Concursus
Compile a declarative DAG of subagents into a deployed, orchestrated team on AWS Bedrock AgentCore.
Concursus (Latin "a running-together / convergence") is cursus's agent-orchestration sibling. Where cursus compiles a pipeline DAG + configs into a SageMaker pipeline, Concursus compiles an AgentDAG + per-agent .agent.yaml manifests into (1) an AgentCore provisioning plan — one CreateAgentRuntime per agent — and (2) a supervisor that dispatches the agents in topological order, wires each agent's declared output into its dependents' input, and routes shared state through AgentCore Memory.
It is the coordinator AgentCore deliberately doesn't ship: AgentCore gives you transport (A2A), tool discovery (Gateway), microVM isolation, identity, memory, and hosting — but no scheduler, dependency graph, or supervisor. You declare a DAG of agents; Concursus provisions them and runs them.
Status: alpha. This release ships the declarative core (
AgentDAG+AgentManifest) and the offline compiler: the dependency resolver, the runtime builder, theOrchestrationAssembler(DAG + manifests → aProvisioningPlan), and the topologicalSupervisor— plus theplan/deploy/runCLI verbs. The compiler is pure-Python; boto3 stays behind the[agentcore]extra and is imported lazily only whendeploy --execute/run --executeactually calls AWS.
Installation
pip install concursus # declarative core (pure Python)
pip install "concursus[agentcore]" # + the AWS Bedrock AgentCore runtime binding (roadmap)
Requires Python 3.10+.
Quick start
Declare a team as an AgentDAG (nodes = agents, edges = data dependencies):
from concursus import AgentDAG
dag = AgentDAG()
for agent in ["ingest", "summarize", "critique", "format"]:
dag.add_node(agent)
dag.add_edge("ingest", "summarize")
dag.add_edge("summarize", "critique")
dag.add_edge("critique", "format")
dag.topological_sort() # ['ingest', 'summarize', 'critique', 'format'] <- dispatch order
dag.validate() # raises if the topology has a cycle
Describe each agent with an .agent.yaml manifest — its AgentCore binding + typed interface:
# summarize.agent.yaml
registry:
container_uri: 111122223333.dkr.ecr.us-east-1.amazonaws.com/summarize-agent:latest
role_arn: arn:aws:iam::111122223333:role/ConcursusAgentRuntimeRole
network_mode: PUBLIC # or VPC
protocol: HTTP # HTTP (/invocations) | MCP (/mcp) | A2A (/)
qualifier: DEFAULT
# ...or reuse an already-deployed agent:
# agent_runtime_arn: arn:aws:bedrock-agentcore:us-east-1:111122223333:runtime/summarize-xyz
contract:
inputs:
document: {type: string}
outputs: # required — the dependency resolver's type gate
summary: {type: string}
key_points: {type: array, items: {type: string}}
spec:
depends_on:
- {from: ingest.document, to: document}
from concursus import AgentManifest
m = AgentManifest.from_yaml("summarize.agent.yaml").validate()
m.protocol # 'HTTP'
m.output_schema # {'summary': {...}, 'key_points': {...}}
Or from the CLI:
concursus info # overview
concursus validate *.agent.yaml # validate manifests
concursus --version
Compile a plan (plan → deploy → run)
Point the compiler at your manifests. Edges are inferred from each manifest's depends_on
(or pass --dag ingest->summarize to set them explicitly). plan prints a JSON
ProvisioningPlan — a topological order, one create_agent_runtime entry per agent, and
the resolved producer→consumer wiring — without touching AWS:
concursus plan *.agent.yaml
from concursus import AgentDAG, AgentManifest, OrchestrationAssembler, Supervisor
manifests = {m.name: m for m in map(AgentManifest.from_yaml, paths)}
dag = AgentDAG()
for name in manifests:
dag.add_node(name)
dag.add_edge("ingest", "summarize").add_edge("summarize", "critique")
plan = OrchestrationAssembler(account="111122223333", region="us-east-1").assemble(dag, manifests)
plan.order # ['ingest', 'summarize', 'critique'] <- dispatch order
plan.to_dict() # JSON-serializable preview (what `concursus plan` prints)
deploy dry-runs what would be created (nothing imported); --execute provisions each agent
end-to-end — ensure its IAM execution role, build + push its image to ECR (when the plan carries a
placeholder URI), then CreateAgentRuntime — reusing an existing image or runtime ARN as-is. run
dry-runs the topological dispatch; --execute invokes the live runtimes, threading each output
into its dependents:
concursus deploy *.agent.yaml # dry-run: the role/image/create steps
concursus deploy *.agent.yaml --execute --source-dir . # + boto3 + docker: role → ECR image → create
concursus run *.agent.yaml --inputs '{"uri": "s3://doc"}' # dry-run the dispatch
concursus run *.agent.yaml --inputs @inputs.json --execute # live InvokeAgentRuntime
outputs = Supervisor(plan, manifests).run({"uri": "s3://doc"}) # {node_id: output_dict}
How it works (the compile target)
Concursus compiles AgentDAG + manifests through validate → resolve → provision → assemble, mapping cursus concepts onto AgentCore primitives:
| cursus | Concursus | AgentCore primitive |
|---|---|---|
PipelineDAG |
AgentDAG |
dispatch order (topological) |
.step.yaml |
.agent.yaml manifest |
container image + roleArn + protocol |
DependencyType enum |
output JSON Schema (mandatory) | the resolver's type gate |
PropertyReference (deferred) |
AgentRef (eager JSONPath) |
InvokeAgentRuntime response |
| step registration | agent registration | CreateAgentRuntime → ARN + V1 + DEFAULT endpoint |
PipelineAssembler → Pipeline |
OrchestrationAssembler → supervisor + plan |
BedrockAgentCoreApp supervisor |
| S3 artifact channels | shared run state | AgentCore Memory |
The supervisor dispatches agents in topological order, invokes each with InvokeAgentRuntime under one runtimeSessionId (session affinity → warm microVMs), extracts each producer's output by JSONPath and injects it into its consumers, and persists outputs to Memory so state survives the ephemeral microVMs.
Durable run state (the StateStore seam)
The supervisor threads every output through a StateStore — an append-only log of validated
outputs plus a derived {node: output} projection (the slipbox's single-source-of-truth /
derived-DB discipline). Two backends share one Protocol:
InProcessStateStore— the zero-dependency, offline default. Nothing new to install.MemoryStateStore— opt-in, AgentCore Memory-backed. Each validated output is one Blob event; a run resumes by replaying its event log, so it survives micro-VM teardown / mid-run crashes — the supervisor skips any node alreadycompleted(). boto3 is imported lazily (the[agentcore]extra); passrun --memory-id <id> [--actor-id <id>] --execute.
Each record also persists its resolved AgentRef edges (consumes), turning the log into a
queryable run graph (RunGraph: upstream/downstream, a structural validate, bounded
context_order). Supervisor.context(node) returns a node's transitive upstream outputs — shared
context as a query, not point-to-point wiring:
from concursus import Supervisor, InProcessStateStore
sup = Supervisor(plan, manifests, state_store=InProcessStateStore())
outputs = sup.run({"uri": "s3://doc"}) # {node_id: output_dict}
sup.context("critique") # {producer: output} for its transitive upstream
Roadmap
- Declarative core:
AgentDAG+AgentManifest(.agent.yaml) + validation + CLI - Dependency resolver over declared output JSON Schemas (
AgentRefwiring + type-gating) -
OrchestrationAssembler: emit an AgentCore provisioning plan (CreateAgentRuntimeper agent + synthesized IAM roles + endpoints) - The supervisor: topological dispatch over
InvokeAgentRuntimewithAgentRefwiring + one stableruntimeSessionId -
plan/deploy/runCLI verbs (deploy/run--executebind boto3 lazily) - Memory-backed shared run state (the
StateStoreseam: in-process default / AgentCore Memory opt-in, replay-resume, the AgentRef link graph +context(node)) - Gateway/A2A node types; a data-driven catalog + recommender of team topologies
License
MIT © Tianpei Xie
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file concursus-0.4.0.tar.gz.
File metadata
- Download URL: concursus-0.4.0.tar.gz
- Upload date:
- Size: 58.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7549003b5ccb9e565dee5d57747bd25bf2b9ebd905cb7558f52c251e87e1d2d2
|
|
| MD5 |
baeb824644fe4f8d9e4c36d08099f08e
|
|
| BLAKE2b-256 |
9129b935d4d90a4211a7f2ff0c45373b2d109ab5f840124f651fc0732c82904e
|
File details
Details for the file concursus-0.4.0-py3-none-any.whl.
File metadata
- Download URL: concursus-0.4.0-py3-none-any.whl
- Upload date:
- Size: 45.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99b14937cadbd3993c16c82fb392b931260dc31e36b117679f547b3924f61bf4
|
|
| MD5 |
dc5a7579784e739deb780d84dd4a0084
|
|
| BLAKE2b-256 |
1a38f8d4a4ca142bbbb7fc0cf64dc60d35ca578fd6350d4195835c2ab214ed64
|